当前位置: 首页 > news >正文

Java大模型开发入门 (8/15):连接外部世界(上) - RAG入门与文档加载

前言

在之前的文章中,我们已经成功地为AI助手赋予了记忆,让它能够进行流畅的多轮对话。然而,它的知识范围仅限于其训练数据截止日期前的公开信息。如果你问它:

  • “我们公司最新的产品‘X-Wing’有什么特性?”
  • “根据内部文档XYZ,服务器部署的第三步是什么?”

它只会回答:“对不起,我不知道。”

如何让大模型能够访问并利用我们私有的、最新的或领域特定的知识库(如产品手册、技术文档、项目Wiki)来回答问题呢?传统的**模型微调(Fine-tuning)**成本高、技术复杂,且不适合知识频繁更新的场景。

目前,业界最主流、最高效的解决方案是——RAG(Retrieval-Augmented Generation,检索增强生成)。从今天开始,我们将用三篇文章的篇幅,一步步在Java中构建一个完整的RAG系统。

第一部分:什么是RAG?它比“喂资料”更聪明

RAG的核心思想非常直观,它模仿了人类开卷考试的过程:

当被问到一个问题时,模型不会只凭自己的“记忆”回答。它会先去我们提供的知识库(一堆文档)里进行检索(Retrieval),找到与问题最相关的几段文字,然后将这些文字作为上下文(Context),连同原始问题一起,**增强(Augmented)地提交给大语言模型,最终让模型基于这些参考资料生成(Generation)**答案。

RAG的工作流程概览:

这个流程可以被拆解为两个阶段:

  1. 数据准备阶段(离线处理)

    • 加载 (Load):从各种来源(文件、URL、数据库等)读取我们的私有文档。
    • 分割 (Split):将长文档切分成更小的、有意义的文本块(Chunks)。
    • 嵌入 (Embed):使用一个专门的“嵌入模型”将每个文本块转换成一个数学向量(一串数字),这个向量代表了文本块的语义。
    • 存储 (Store):将文本块和其对应的向量存入一个专门的“向量数据库”中,以便快速检索。
  2. 问答阶段(在线处理)

    • 嵌入用户问题:同样地,将用户的提问也转换成一个向量。
    • 检索 (Retrieve):在向量数据库中,搜索与用户问题向量最“相似”的几个文本块向量。
    • 增强 (Augment):将检索到的文本块(作为上下文)和原始问题组合成一个新的提示(Prompt)。
    • 生成 (Generate):将这个增强后的提示发送给大语言模型,生成最终答案。

今天,我们将聚焦于数据准备阶段的前两个步骤:加载(Load)分割(Split)

第二部分:文档加载 (Loading) - 万物皆可为知识

LangChain4j提供了丰富的DocumentLoader来帮助我们从各种来源加载数据。一个Document对象在LangChain4j中不仅仅是文本内容,它还包含了元数据(Metadata),比如文件来源、作者等信息,这在后续的高级应用中非常有用。

实战:加载一个本地的.txt.pdf文件

  1. 准备文档
    在你的Spring Boot项目的src/main/resources目录下,创建一个名为documents的文件夹。在里面放入一个简单的.txt文件,例如product-info.txt

    product-info.txt 内容示例:

    Product Name: X-Wing AI Assistant
    Version: 2.0
    Release Date: 2025-06-15Key Features:
    - Multi-language support: English, Spanish, and Japanese.
    - Advanced memory module for long-term context retention.
    - Tool integration capability for calling external APIs.
    - Secure and private, all data is processed on-premise.Setup Instructions:
    The setup requires a minimum of 16GB RAM and a 4-core CPU.
    Java 17 is mandatory for running the application.
    The main configuration file is located at /etc/xwing/config.properties.
    
  2. 添加PDF加载依赖(可选)
    如果想加载PDF,你需要添加Apache PDFBox的依赖到pom.xml中:

    <dependency><groupId>org.apache.pdfbox</groupId><artifactId>pdfbox</artifactId><version>2.0.31</version> <!-- 或一个更新的版本 -->
    </dependency>
    
  3. 编写加载代码
    让我们创建一个新的Service来处理文档。在service包下创建DocumentService.java

    package com.example.aidemoapp.service;import dev.langchain4j.data.document.Document;
    import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
    import org.springframework.stereotype.Service;import java.nio.file.Path;
    import java.nio.file.Paths;@Service
    public class DocumentService {public void loadDocument() {// 获取项目根路径下的 "src/main/resources/documents/product-info.txt" 文件Path documentPath = Paths.get("src/main/resources/documents/product-info.txt");// 使用FileSystemDocumentLoader加载文档Document document = FileSystemDocumentLoader.loadDocument(documentPath);System.out.println("--- Document Loaded ---");System.out.println("Content: " + document.text());System.out.println("Metadata: " + document.metadata());}
    }
    

    你可以创建一个临时的Controller或在主程序中调用这个loadDocument方法来测试,你会看到文件内容和元数据被成功打印出来。

第三部分:文档分割 (Splitting) - 化整为零的艺术

为什么要分割文档?

  • Token限制:大模型一次能处理的文本长度(上下文窗口)是有限的。我们不能把一本几百页的书一次性塞给它。
  • 检索精度:将文档切分成小块,可以让我们更精确地定位到与用户问题最相关的具体段落,而不是整个冗长的文档。
  • 成本效益:发送给模型的文本越长,API调用成本越高。只发送最相关的片段可以有效降低成本。

LangChain4j提供了DocumentSplitter接口和多种实现策略。最常用的一种是递归分割(Recursive Splitting)。它会尝试按顺序使用一组分隔符(如\n\n, \n, , ``)来切分文本,以求在保持语义完整性的前提下,将文本块切分到指定的大小。

实战:分割我们加载的文档

修改DocumentService.java

package com.example.aidemoapp.service;import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser; // 明确指定解析器
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import org.springframework.stereotype.Service;import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;@Service
public class DocumentService {public void loadAndSplitDocument() {Path documentPath = Paths.get("src/main/resources/documents/product-info.txt");// 对于txt文件,最好明确指定一个解析器Document document = FileSystemDocumentLoader.loadDocument(documentPath, new TextDocumentParser());// 创建一个文档分割器// 参数1: maxSegmentSize - 每个片段的最大字符数// 参数2: maxOverlap - 相邻片段之间的重叠字符数,有助于保持上下文连续性DocumentSplitter splitter = DocumentSplitters.recursive(300, 10);// 使用分割器来分割文档List<TextSegment> segments = splitter.split(document);System.out.println("--- Document Split into " + segments.size() + " Segments ---");for (int i = 0; i < segments.size(); i++) {System.out.println("--- Segment " + (i+1) + " ---");System.out.println(segments.get(i).text());}}
}

再次运行这个方法,你会看到原本一整篇的文档,被巧妙地分割成了多个长度在300个字符左右、且互相有少量重叠的文本片段(TextSegment)。

总结

今天,我们成功地开启了构建RAG系统的第一步,完成了数据准备阶段最基础的加载分割工作。我们学会了:

  • RAG系统模仿人类“开卷考试”的核心思想。
  • 使用DocumentLoader从文件系统加载我们的私有文档。
  • 使用DocumentSplitter将长文档切分成有意义的、大小合适的文本片段。

现在我们有了一堆“知识碎片”,但计算机还无法理解它们的含义。如何让计算机能像人一样,理解“这个片段讲的是产品特性”和“那个片段讲的是安装要求”?

这需要我们将这些文本片段转换成机器能理解的语言——数学向量。这个过程,就是嵌入(Embedding)


下一篇预告:
Java大模型开发入门 (9/15):连接外部世界(中) - 向量嵌入与向量数据库》—— 我们将深入RAG的核心技术,理解什么是文本嵌入(Embedding),并学会使用嵌入模型将我们的文档片段转换为向量。同时,我们会初次接触“向量数据库”这个新概念,并将转换后的向量存入一个简单的内存向量库中。

相关文章:

  • Ubuntu安装Gym及其仿真
  • 永磁同步电机控制算法--双矢量模型预测转矩控制MPTC(占空比)
  • Keepalived 高可用
  • MACD指标
  • java中扩展运算符
  • <11>-MySQL事务管理
  • 算法训练第十七天
  • Hugging face 和 魔搭
  • 浅拷贝 与 深拷贝
  • LeetCode - 35. 搜索插入位置
  • 戴维南端接与 RC端接
  • static的三种作用
  • 重读《人件》Peopleware -(16)Ⅲ 适当人选 Ⅰ霍恩布洛尔因素(上)
  • callback的原理和机制
  • <10>-MySQL索引特性
  • 【电声耦合】TaOsSi和NbOsSi超导中的电子-声子耦合
  • c++编译第三方项目报错# pragma warning( disable: 4273)
  • 多线程下 到底是事务内部开启锁 还是先加锁再开启事务?
  • AnimateCC及CreateJS:打飞机的4版本V1、V2、V3、V4
  • hot100 -- 12.栈系列
  • wordpress 网校主题/长沙网站seo收费标准
  • 电脑维护网站模板/免费个人网站模板
  • 做网站流行的/360免费建站官网
  • 建设银行网站的登录验证程序安全吗/南阳本地网络推广优化公司
  • 如何看网站空间问题/seo站内优化培训
  • 南京网站制作/在线发外链工具