让 AI 更懂行:一文读懂检索增强生成(RAG)

你有没有过这样的经历?问 AI 一个专业问题,比如 “2024 年最新的新能源汽车补贴政策”,它却给你一套 2022 年的答案;或者让它解释 “量子计算中的量子隧穿效应”,结果说出来的内容似是而非,甚至有明显错误。

这不是 AI “不努力”,而是大语言模型(LLM)天生有两个短板:知识有保质期(训练数据截止到某个时间点,之后的新信息不知道)、容易 “瞎编”(遇到不懂的问题可能硬凑答案)。

而今天要讲的检索增强生成(RAG),就是给 AI “装个外挂”,让它既能回答最新知识,又能说得更靠谱。

一、为什么需要RAG?专业知识的获取之道

大型语言模型(LLM)虽然强大,但它的知识仅限于训练数据。当需要处理专业领域知识或私有数据时,有三种主要解决方案:

  1. RAG(检索增强生成)

    • 实时从外部知识库检索信息

    • 将检索结果与问题一起交给LLM生成答案

    • 优势:知识实时更新,成本低

    • 场景:客服系统、实时金融数据查询

  2. 模型微调(Fine-tuning)

    • 使用专业数据重新训练模型

    • 调整模型参数适应特定领域

    • 优势:响应速度快(单次模型调用)

    • 场景:自动驾驶系统、专业文档生成

  3. 混合策略

    • 结合RAG和微调的优势

    • 基础能力通过微调实现

    • 实时知识通过RAG补充

选择指南
✅ 优先微调:自动驾驶、实时翻译等毫秒级响应场景
✅ 优先RAG:客服机器人、股票咨询等知识频繁更新场景
✅ 混合方案:医疗诊断等复杂专业场景

举个例子:

  • 如果你做 “古诗生成” APP,用微调更合适(古诗知识几百年不变)。
  • 如果你做 “电商客服机器人”,用 RAG 更合适(商品价格、库存天天变,改改知识库就行)。

二、什么是 RAG?给 AI 开 “卷” 考试的能力

检索增强生成(Retrieval-Augmented Generation),简单说就是:
在 AI 回答问题前,先让它从你的专属知识库(比如公司文档、行业报告、最新新闻)里 “搜资料”,再把搜到的信息和问题一起交给 AI,让它基于这些资料生成答案。

打个比方:

  • 没有 RAG 的 AI,像闭卷考试的学生,全靠记忆答题,记不清就可能错。
  • 有 RAG 的 AI,像开卷考试的学生,答题时可以翻书、查笔记,答案既准又新。

三、RAG vs 微调:两种让 AI “变专业” 的方法怎么选?

除了 RAG,还有一种让 AI 懂专业知识的方法叫 “微调”(Fine-tuning)。两者的区别,用生活例子一看就懂:

对比维度微调(Fine-tuning)RAG(检索增强生成)
原理用专业数据重新 “训练” AI,让它把知识 “背下来”不训练 AI,让它答题时 “临时查资料”
知识更新难(更新后需要重新 “背”,成本高)易(直接改知识库内容,AI 下次查就自动获取新信息)
成本高(需要大量数据和计算资源,像请老师一对一补课)低(只需准备知识库,像给学生配一本参考书)
适用场景知识稳定、少更新的领域:
比如古诗创作(唐诗宋词不会变)、法律条文解读(几年才修订一次)
知识频繁更新的领域:
比如电商客服(商品价格天天变)、新闻问答(每天都有新事件)、股票咨询(实时行情更新)
 

混合策略:复杂场景可以结合两者优势。比如先微调一个 “基础版” AI(让它熟悉行业术语),再用 RAG 接入实时数据(让它获取最新信息),像金融领域的 AI 助手,既懂金融术语,又能查当天股市行情。

三、RAG 的 “核心引擎”:向量搜索技术详解

RAG 能快速找到 “相关资料”,靠的是向量搜索(语义搜索)。它比传统的 “关键词搜索” 聪明得多,我们一步步拆解:

1. 向量:给文字编一串 “数字密码”

你可能会问:“文字怎么能被‘搜索’呢?” 向量搜索的第一步,是把文字转换成 “数字向量”。

 
  • 什么是向量? 就是一串数字,用来描述文字的 “语义特征”。
    比如:

    • “猫” 的向量:[0.8, 0.2, -0.5, 0.3](这串数字代表 “动物、毛茸茸、会叫、小型” 等特征)
    • “狗” 的向量:[0.7, 0.3, -0.4, 0.2](和 “猫” 的向量很像,因为都是宠物)
    • “汽车” 的向量:[-0.6, 0.9, 0.2, -0.1](和 “猫” 的向量差别很大)

    简单说:意思越接近的文字,向量越相似

2. 维度:描述 “语义特征” 的角度

向量的 “维度”,就是描述文字的 “特征角度”。维度越多,描述越精准。

  • 比如描述 “一杯奶茶”,可以有这些维度(特征):

    维度(特征)数值(0-10 分)
    甜度8(全糖)
    冰度5(少冰)
    是否加珍珠1(是)
    价格(元)18
     

    这杯奶茶的向量就是:[8, 5, 1, 18]

  • 再比如描述 “一篇文章”,维度可能是 “主题(科技 / 娱乐)”“情感(正面 / 负面)”“篇幅(长 / 短)” 等,每个维度用数字表示,最终形成一个多维度向量。

3. 相似度计算:判断两句话 “像不像”

向量搜索的核心是 “找相似”。比如用户搜 “轿车”,我们希望返回 “汽车”“vehicle” 等相关内容,这就要靠 “相似度计算”。

最常用的是余弦相似度(不用记公式,理解逻辑即可):

  • 两个向量 “方向越接近”,余弦相似度越高(比如 “轿车” 和 “汽车” 的向量,方向几乎一致,相似度≈0.9)。
  • 两个向量 “方向越相反”,余弦相似度越低(比如 “热水” 和 “冰块” 的向量,方向相反,相似度≈-0.8)。

举个例子:

  • 用户问 “什么车适合家庭用?”,向量是 A。
  • 知识库中 “SUV 空间大,适合家庭出行” 的向量是 B,“跑车速度快,适合单人驾驶” 的向量是 C。
  • 计算发现 A 和 B 的余弦相似度(0.8)远高于 A 和 C(0.2),所以优先返回 B 对应的内容。

相似度计算四大方法

方法特点适用场景
余弦相似度专注方向差异语义匹配
欧几里得距离计算空间直线距离简单数值匹配
点积考虑方向和大小综合匹配
曼哈顿距离计算网格路径距离分类特征匹配

4. 为什么向量搜索比关键词搜索强?

  • 关键词搜索的局限:比如搜 “苹果”,只会返回带 “苹果” 二字的内容,可能漏掉 “iPhone”“Mac”(虽然都是苹果公司产品)。
  • 向量搜索的优势:能 “理解语义”。搜 “苹果手机” 时,会找到向量相似的 “iPhone”“苹果 15” 等内容,哪怕文字里没直接出现 “苹果手机” 四个字。

四、RAG 完整工作流程:从 “喂资料” 到 “答问题”

RAG 的工作过程分两大阶段:索引阶段(提前准备知识库)和检索阶段(实时回答问题)。我们结合具体工具(以 LangChain4j 为例)拆解每一步:

阶段 1:索引阶段 —— 给 AI “喂资料”(离线准备)

这一步是提前处理知识库,让 AI 后续能快速查到资料。流程如下:

1. 加载文档:把你的资料 “搬进系统”

需要把各种格式的文档(TXT、PDF、Word、网页、Excel 等)加载到系统中。LangChain4j 提供了多种 “文档加载器”:

文档来源工具类用法示例
本地文件FileSystemDocumentLoader加载D:/知识库/产品手册.txt
网页内容UrlDocumentLoader加载https://www.example.com/news的网页内容
阿里云 OSSAliyunOssDocumentLoader加载阿里云 OSS 存储的文档
腾讯云 COSTencentCosDocumentLoader加载腾讯云 COS 存储的文档
GitHub 仓库GitHubDocumentLoader加载 GitHub 仓库里的代码或文档

代码示例(加载本地 TXT)

import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;


@SpringBootTest
public class RAGTest {
    @Test
    public void testReadDocument() {
        //使用FileSystemDocumentLoader读取指定目录下的知识库文档
        // 加载本地TXT文档,默认用TextDocumentParser解析
        Document document = FileSystemDocumentLoader.loadDocument("D:/知识库/产品手册.txt");
        System.out.println("文档内容:" + document.text()); // 打印文档内容

    }
}
如果是 PDF/Word,需要指定解析器

比如解析 PDF,先添加依赖:

<!-- 解析PDF的依赖 -->
        <dependency>
            <groupId>dev.langchain4j</groupId>
            <artifactId>langchain4j-document-parser-apache-pdfbox</artifactId>
            <version>${langchain4j.version}</version> 
        </dependency>

再用代码加载:

        // 加载并解析PDF文档(指定ApachePdfBoxDocumentParser)
        Document pdfDoc = FileSystemDocumentLoader.loadDocument(
                "D:/知识库/医疗指南.pdf",
                new ApachePdfBoxDocumentParser() // 专门解析PDF的工具
        );
        System.out.println("PDF文档内容:" + pdfDoc.text());

常见的文档加载器

  • 来自 langchain4j 模块的文件系统文档加载器(FileSystemDocumentLoader
  • 来自 langchain4j 模块的类路径文档加载器(ClassPathDocumentLoader
  • 来自 langchain4j 模块的网址文档加载器(UrlDocumentLoader
  • 来自 langchain4j-document-loader-amazon-s3 模块的亚马逊 S3 文档加载器 AmazonS3DocumentLoader
  • 来自 langchain4j-document-loader-azure-storage-blob 模块的 Azure Blob 存储文档加载器AzureBlobStorageDocumentLoader
  • 来自 langchain4j-document-loader-github 模块的 GitHub 文档加载器(GitHubDocumentLoader
  • 来自 langchain4j-document-loader-google-cloud-storage 模块的谷歌云存储文档加载器 GoogleCloudStorageDocumentLoader
  • 来自 langchain4j-document-loader-selenium 模块的 Selenium 文档加载器 SeleniumDocumentLoader
  • 来自 langchain4j-document-loader-tencent-cos 模块的腾讯云对象存储文档加载器 TencentCosDocumentLoader
2. 文档分割:把长文档切成 “小片段”

为什么要分割?

  • 大模型有 “上下文窗口限制”(比如 GPT-3.5 最多处理 4096 个 token,约 8000 汉字),太长的文档塞不进去。
  • 片段越小,搜索越精准(比如查 “电池续航”,不用加载整本书,只需要相关的几小段)。

分割工具:LangChain4j 提供多种 “文档分割器”,常用的有:

分割器类型特点适用场景
按段落分割按空行分割(适合散文、文档)结构松散的文本
按句子分割按句号 / 感叹号分割(用 OpenNLP 库)短句多的文本(如新闻、对话)
按 token 分割按模型可识别的 token 数分割(最常用)所有类型,尤其是技术文档
递归分割(推荐)先按大单元(段落)分割,太大再切句子复杂文档(混合段落、长句)

关键参数

  • maxSegmentSize:每个片段的最大长度(以 token 为单位,1token≈2 汉字)。
  • overlapSize:相邻片段的重叠长度(保证上下文连贯)

代码示例(递归分割)

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-hugging-face</artifactId>
    <version>${langchain4j.version}</version> <!-- 与核心版本一致:1.0.0-beta3 -->
</dependency>
// 定义分割规则:最大300token,重叠30token
DocumentSplitter splitter = DocumentSplitters.recursive(
    300,  // 每个片段最多300token(约600字)
    30,   // 相邻片段重叠30token(约60字)
    new HuggingFaceTokenizer() // 用tokenizer计算长度
);

// 分割文档(把整个文档切成多个小片段)
List<TextSegment> segments = splitter.split(document);
System.out.println("分割后片段数:" + segments.size());

参数怎么设才合理?

  • maxSegmentSize:参考大模型的上下文窗口,比如模型支持 4096token,就设 300-800(给问题和指令留空间)。
    • 技术文档(如 API 手册):设大一点(600-800),避免公式 / 代码被切碎。
    • 新闻 / 对话:设小一点(300-500),提高搜索精度。
  • overlapSize:20-50token 为宜。
    • 逻辑严密的文本(如论文):重叠大一点(40-50),避免分割断裂。
    • 独立短句(如商品列表):重叠小一点(20-30),减少冗余。
  • token和token计算:
    • DeepSeekToken 用量计算 | DeepSeek API Docs
    • 阿里百炼:百炼控制台
    •     @Test
          public void testTokenCount() {
              String text = "这是一个示例文本,用于测试 token 长度的计算。";
              UserMessage userMessage = UserMessage.userMessage(text);
      //计算 token 长度
      //QwenTokenizer tokenizer = new QwenTokenizer(System.getenv("DASH_SCOPE_API_KEY"),"qwen-max");
              HuggingFaceTokenizer tokenizer = new HuggingFaceTokenizer();
              int count = tokenizer.estimateTokenCountInMessage(userMessage);
              System.out.println("token长度:" + count);
          }

3. 文本嵌入:把片段转换成向量

用 “嵌入模型”(Embedding Model)把每个文本片段转换成向量。嵌入模型就像 “翻译器”,能把文字翻译成向量。

常用嵌入模型

  • 开源:Sentence-BERT(轻量,适合本地部署)、BGE(中文支持好)。
  • 闭源:OpenAI Embedding(精度高,需 API 密钥)、阿里云百炼 Embedding。

        // 创建向量数据库
        EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();

        // 把一个文本片段转换成向量
        TextSegment segment = segments.get(0); // 取第一个片段
        Embedding embedding = embeddingModel.embed(segment.text()).content();
        System.out.println("向量维度:" + embedding.vector().length); 
总代码:
    /**
     * 加载文档并存入向量数据库
     */
    @Test
    public void testReadDocumentAndStore() {
        // 加载本地TXT文档,默认用TextDocumentParser解析
        Document document = FileSystemDocumentLoader.loadDocument("F:/code/java-ai-langchain4j/src/main/resources/xiaozhi-prompt-template.txt");
        System.out.println("文档内容:" + document.text()); // 打印文档内容
        // 定义分割规则:最大300token,重叠30token
        DocumentSplitter splitter = DocumentSplitters.recursive(
                300,  // 每个片段最多300token(约600字)
                30,   // 相邻片段重叠30token(约60字)
                new HuggingFaceTokenizer() // 用tokenizer计算长度
        );

        // 分割文档(把整个文档切成多个小片段)
        List<TextSegment> segments = splitter.split(document);
        System.out.println("分割后片段数:" + segments.size());

        // 创建向量数据库
        EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel(); // 直接使用预定义模型

        // 把一个文本片段转换成向量
        TextSegment segment = segments.get(0); // 取第一个片段
        Embedding embedding = embeddingModel.embed(segment.text()).content();
        System.out.println("向量维度:" + embedding.vector().length);
    }
终极理解

其实文本向量(或者说 “文本的数字表示”)的核心作用很简单:把人类能看懂的文字,翻译成计算机能 “理解” 的数字。咱们结合代码,用生活化的例子一步步说清楚:

一、为什么需要 “文本向量”?

计算机天生不认识文字。比如 “苹果” 这两个字,对计算机来说就是一串字符,它不知道这是一种水果。但如果把 “苹果” 转换成一串数字(比如 [0.2, 0.5, -0.1, ...]),计算机就能通过比较这些数字的 “相似度”,判断两句话的意思是否相近。

 

举个例子:

 
  • “我想吃苹果” → 向量 A
  • “我想吃香蕉” → 向量 B
  • “我想买汽车” → 向量 C
 

向量 A 和向量 B 的数字差异会很小(因为都是 “想吃的水果”),而 A 和 C 的差异会很大(一个是水果,一个是交通工具)。
这就是向量的核心价值:用数字捕捉文字的 “语义含义”,让计算机能比较文字的相似性

二、代码里,向量是怎么来的?

代码做了三件关键的事,最终目的就是生成这些 “语义数字”:

1. 把文档切成小片段(DocumentSplitter

为什么要分割?
如果把一篇长文档(比如 1000 字)转换成一个向量,这个向量很难精确表达文档里的每个细节(比如前面讲 “苹果”,后面讲 “香蕉”,向量会模糊两者的差异)。
就像把一本厚书浓缩成一句话,会丢失很多信息。
代码里把文档切成 “最多 300 个 token(约 600 字)、重叠 30 个 token” 的小片段,每个片段更聚焦一个局部主题,方便后续生成更精确的向量。

2. 用模型把片段转换成向量(AllMiniLmL6V2EmbeddingModel

这个模型就像一个 “翻译官”:输入文字,输出一串数字(向量)。
它是怎么 “翻译” 的?
这个模型是用海量文本训练出来的(比如互联网上的文章、书籍),训练过程中它会学习文字的 “上下文规律”。比如它会发现:

 
  • “苹果” 经常和 “水果”“吃” 一起出现;
  • “医生” 经常和 “医院”“看病” 一起出现。
 

所以当输入 “苹果是一种水果”,它会输出一串数字,这些数字综合了 “苹果”“水果” 的语义特征 —— 这串数字就是向量。

3. 向量的 “维度” 是什么意思?

代码里打印的 “向量维度:768”,意思是每个文本片段的向量由 768 个数字组成。
可以理解成:用 768 个 “角度” 来描述一个文本的特征
比如:

 
  • 第 1 个数字:描述 “是否和食物相关”;
  • 第 2 个数字:描述 “是否和动物相关”;
  • ...
  • 第 768 个数字:描述 “是否带有情感色彩”。
 

(实际每个维度的含义很复杂,是模型自动学习的,不用深究具体每个数字代表什么,知道它们共同描述语义即可。)

三、向量有什么用?(结合你的代码后续场景)

你的代码目前只做了 “生成向量”,但向量的真正价值在后续的 “检索” 或 “问答” 中:
比如用户问 “什么水果适合减肥?”,你可以:

 
  1. 把问题也转换成一个向量(比如 [0.3, 0.4, -0.2, ...]);
  2. 拿这个问题向量,和你之前生成的所有文本片段向量做比较;
  3. 找到和问题向量最相似的片段(比如包含 “苹果热量低,适合减肥” 的片段);
  4. 用这个片段的内容回答用户的问题。
 

这就是现在流行的 “RAG(检索增强生成)” 的核心逻辑 ——用向量找相关内容,让回答更精准

总结一下

文本向量就是:

 
  • 文字的 “数字翻译版”;
  • 用一串数字描述文字的语义(相似的文字,数字也相似);
  • 你的代码里,分割文档是为了让向量更精准,模型是 “翻译官”,768 维是描述语义的 “角度数量”。
 

这样是不是就好理解了?本质上就是把文字变成计算机能比较的数字,方便后续的语义匹配~

4. 向量存储:把向量和片段存进数据库

需要专门的 “向量数据库” 存储向量和对应的文本片段,方便后续快速搜索。

常用向量数据库

数据库特点适用场景
InMemory内存中的数据库,无需部署测试、小数据量(<1 万片段)
Milvus(开源)支持大规模数据(亿级向量)企业级应用、大数据量
Pinecone(云)托管服务,无需维护服务器快速上线、不想搭环境
Qdrant(开源)轻量,支持多种相似度计算中小规模应用、本地部署

代码示例(用内存数据库存储)

// 初始化内存向量数据库
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();

// 存储所有片段和对应的向量
for (TextSegment segment : segments) {
    Embedding embedding = embeddingModel.embed(segment.text());
    embeddingStore.add(embedding, segment); // 向量→片段一一对应存储
}

小技巧:实际开发中,可直接用EmbeddingStoreIngestor一键完成 “分割→嵌入→存储”:

// 一键处理:加载文档→分割→转向量→存数据库
EmbeddingStoreIngestor.ingest(
    document,          // 原始文档
    embeddingStore,    // 向量数据库
    embeddingModel,    // 嵌入模型
    splitter           // 分割器
);

阶段 2:检索阶段 ——AI “查资料答题”(实时处理)

当用户提问时,系统实时完成以下步骤:

1. 处理问题:把用户的问题转换成向量
// 用户提问
String question = "我们公司新款扫地机器人有哪些传感器?";

// 把问题转换成向量
Embedding questionEmbedding = embeddingModel.embed(question);
2. 检索相似片段:从向量数据库找相关资料
// 从数据库中找和问题向量最相似的前3个片段
List<EmbeddingMatch<TextSegment>> matches = embeddingStore.findRelevant(
    questionEmbedding, // 问题向量
    3                  // 最多返回3个最相关的片段
);

// 提取找到的文本片段
List<String> relevantDocs = matches.stream()
    .map(match -> match.embedded().text()) // 获取片段内容
    .collect(Collectors.toList());
3. 生成答案:让大模型结合资料回答

把 “问题 + 找到的片段” 一起发给大模型,让它基于资料生成答案:

// 初始化大模型(以OpenAI为例)
ChatLanguageModel model = OpenAiChatModel.withApiKey("你的API密钥");

// 构建提示词(问题+相关资料)
String prompt = "根据以下资料回答问题:\n" +
                String.join("\n", relevantDocs) + "\n" +
                "问题:" + question;

// 调用大模型生成答案
String answer = model.generate(prompt);
System.out.println("答案:" + answer);

输出示例

答案:根据产品手册,新款扫地机器人搭载了三种传感器:激光雷达(用于建图导航)、3D结构光(识别障碍物)和悬崖传感器(防止跌落)。
4.工作方式
(1)  实例化一个 文档分割器 DocumentSplitter ),指定所需的 文本片段 TextSegment )大小,并且可以选择指定characters token 的重叠部分。
(2) “ 文档分割器 DocumentSplitter )将给定的文档( Document )分割成更小的单元,这些单元的性 质因分割器而异。例如,“ 按段落分割文档器 DocumentByParagraphSplitter )将文档分割成段落(由两个或更多连续的换行符定义),而 “ 按句子分割文档器 DocumentBySentenceSplitter )使 用 OpenNLP 库的句子检测器将文档分割成句子,依此类推。
(3)然后, 文档分割器 DocumentSplitter )将这些较小的单元(段落、句子、单词等)组合成 文本片段” TextSegment ),尝试在单个 文本片段 TextSegment )中包含尽可能多的单元,同时不超过第一步中设置的限制。如果某些单元仍然太大,无法放入一个 “ 文本片段 TextSegment )中,它会调用一个子分割器。这是另一个 “ 文档分割器 DocumentSplitter ),能够将不适合的单元分割成更细粒度的单元。会向每个文本片段添加一个唯一的元数据条目 “index” 。第一个 文本片段” TextSegment )将包含 index=0 ,第二个是 index=1 ,依此类推
模型上下文窗口 可以通过模型参数列表查看: 阿里云百炼
期望的文本片段最大大小
1. 模型上下文窗口 :如果你使用的大语言模型( LLM )有特定的上下文窗口限制,这个值不能超过模型能够处理的最大 token 数。例如,某些模型可能最大只能处理 2048 token ,那么设置的文本片段大小就需要远小于这个值,为后续的处理(如添加指令、其他输入等)留出空间。通常,在这种情况下,你可以设置为 1000 - 1500 左右,具体根据实际情况调整。
 
2. 数据特点 :如果你的文档内容较为复杂,每个段落包含的信息较多,那么可以适当提高这个值,
比如设置为 500 - 800 token ,以便在一个文本片段中包含相对完整的信息块。相反,如果文档段落较短且信息相对独立,设置为 200 - 400 token 可能就足够了。
 
3. 检索需求 :如果希望在检索时能够更精确地匹配到相关信息,较小的文本片段可能更合适,这样
可以提高信息的粒度。例如设置为 200 - 300 token 。但如果更注重获取完整的上下文信息,较大的文本片段(如 500 - 600 token )可能更有助于理解相关内容。
 
重叠部分大小
1. 上下文连贯性 :重叠部分的主要作用是提供上下文连贯性,避免因分割导致信息缺失。如果文档
内容之间的逻辑联系紧密,建议设置较大的重叠部分,如 50 - 100 token ,以确保相邻文本片段
之间的过渡自然,模型在处理时能够更好地理解上下文。
 
2. 数据冗余 :然而,设置过大的重叠部分会增加数据的冗余度,可能导致处理时间增加和资源浪
费。因此,需要在上下文连贯性和数据冗余之间进行平衡。一般来说, 20 - 50 token 的重叠是比较常见的取值范围。
 
3. 模型处理能力 :如果使用的模型对输入的敏感性较高,较小的重叠部分(如 20 - 30 token )可能 就足够了,因为过多的重叠可能会引入不必要的干扰信息。但如果模型对上下文依赖较大,适当增 加重叠部分(如 40 - 60 token )可能会提高模型的性能。
例如,在处理一般性的文本资料,且使用的模型上下文窗口较大(如 4096 token )时,设置文本片段 最大大小为 600 - 800 token ,重叠部分为 30 - 50 token 可能是一个不错的选择。但最终的设置还需 要通过实验和实际效果评估来确定,以找到最适合具体应用场景的参数值

五、新手必踩的 3 个坑及避坑指南

  1. 坑 1:文档分割不合理,导致答案漏信息

    • 症状:用户问的问题明明在文档里,AI 却答不上来。
    • 原因:片段切得太小,把相关内容切成了两半,检索时只找到了一半。
    • 解决:增大overlapSize(比如从 30token 调到 50),保证相邻片段有足够重叠。
  2. 坑 2:向量数据库选不对,性能差

    • 症状:数据量超过 1 万条后,检索速度变慢(超过 1 秒)。
    • 原因:用了 InMemory 内存数据库(只适合测试)。
    • 解决:换成 Milvus 或 Pinecone,支持大规模数据的高效检索。
  3. 坑 3:嵌入模型选得不好,语义理解差

    • 症状:用户问 “轿车”,AI 却返回 “火车” 的内容。
    • 原因:用了通用嵌入模型,对专业术语理解不足。
    • 解决:中文场景优先用 “BGE 中文模型”,专业领域(如医疗)用领域专用嵌入模型。

六、向量模型和向量存储

1、向量大模型

1.1、介绍
text-embedding-v3: 阿里云百炼
使用通用文本向量 text-embedding-v3 ,维度 1024 ,维度越多,对事务的描述越精准,信息检索的精度越高
1.2模型配置
使用 text-embedding-v3 依然需要添加 langchain4j-community-dashscope 依赖,我们之前文章(一)的时候已经添加过了
配置向量模型
#集成阿里通义千问-通用文本向量-v3
langchain4j.community.dashscope.embedding-model.api-key=${DASH_SCOPE_API_KEY}
langchain4j.community.dashscope.embedding-model.model-name=text-embedding-v3

文本向量化

@SpringBootTest
public class EmbeddingTest {
    @Autowired
    private EmbeddingModel embeddingModel;


    @Test
    public void testEmbeddingModel() {
        Response<Embedding> embed = embeddingModel.embed("你好");
        System.out.println("向量维度:" + embed.content().vector().length);
        System.out.println("向量输出:" + embed.toString());
    }
}

如果出现bean的错误则添加下面的配置

import dev.langchain4j.model.embedding.onnx.allminilml6v2.AllMiniLmL6V2EmbeddingModel;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import dev.langchain4j.model.embedding.EmbeddingModel;


@Configuration  // 标记为配置类
public class LangChain4jConfig {

    // 将EmbeddingModel注册为Bean,供Spring自动注入
    @Bean
    public EmbeddingModel embeddingModel() {
        return new AllMiniLmL6V2EmbeddingModel();  // 你使用的嵌入模型
    }
}

2、向量存储

2.1Pinecone简介
之前我们使用的是 InMemoryEmbeddingStore 作为向量存储,但是不建议在生产中使用基于内存的向量存 储。因此这里我们使用Pinecone 作为向量数据库。
访问官方网站、注册、登录、获取 apiKey 且配置在环境变量中。 默认有 2GB 的免费存储空间
2.2 Pinecone 的使用
得分的含义
在向量检索场景中,当我们把查询文本转换为向量后,会在嵌入存储( EmbeddingStore )里查找与之最相似的向量(这些向量对应着文档片段等内容)。为了衡量查询向量和存储向量之间的相似程度,会使用某种相似度计算方法(例如余弦相似度等)来得出一个数值,这个数值就是得分。得分越高,表明查询向量和存储向量越相似,对应的文档片段与查询文本的相关性也就越高。
得分的作用
  • 筛选结果:通过设置 minScore 阈值,能够过滤掉那些与查询文本相关性较低的结果。在代码 里, minScore(0.8) 意味着只有得分大于等于 0.8 的结果才会被返回,低于这个阈值的结果会被舍弃。这样可以确保返回的结果是与查询文本高度相关的,提升检索结果的质量。
  • 控制召回率和准确率:调整 minScore 的值可以在召回率和准确率之间进行权衡。如果把阈值设置得较低,那么更多的结果会被返回,召回率会提高,但可能会包含一些相关性不太强的结果,导致准确率下降;反之,如果把阈值设置得较高,返回的结果数量会减少,准确率会提高,但可能会遗漏一些相关的结果,使得召回率降低。在实际应用中,需要根据具体的业务需求来合理设置 minScore 的值。
示例说明
假设我们有一个关于水果的文档集合,嵌入存储中存储了这些文档片段的向量。当我们使用 苹果的营养价值” 作为查询文本时,向量检索会计算查询向量与存储向量的相似度得分。如果 minScore 设置为0.8,那么只有那些与 苹果的营养价值 相关性非常高的文档片段才会被返回,而一些只简单提及苹果但没有详细讨论其营养价值的文档片段可能由于得分低于 0.8 而不会被返回。
2.3 、集成 Pinecone
参考文档: Pinecone | LangChain4j
添加依赖:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-pinecone</artifactId>
</dependency>

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐