本文档用于记录 RAG(Retrieval Augmented Generation,检索增强生成)核心技术实现,覆盖企业 AI 落地 90% 场景所需的核心模块,是简历核心技术亮点的重要支撑。文档基于 Spring AI 框架实现,整合文本处理、向量嵌入、向量数据库及完整 RAG 流程,可直接用于本地学习、Demo 搭建及企业级基础落地。
一、技术概述
1.1 什么是 RAG
RAG 即检索增强生成,是结合「信息检索」与「生成式 AI」的核心技术,解决生成式 AI 幻觉、知识滞后、无法结合私有数据回答的核心痛点。其核心逻辑是:用户提问时,先从私有知识库(向量数据库)中检索出最相关的知识片段,再将知识片段与提问结合,交给大模型生成准确、有依据的回答。
核心价值:无需微调大模型,即可让 AI 结合企业私有数据(文档、手册、知识库等)回答问题,降低企业 AI 落地成本,提升回答准确性和实用性,是企业 AI 落地的首选方案。
1.2 技术栈说明
- 核心框架:Spring AI 1.0.1(适配 Java 生态,企业级首选)
- 文本处理:spring-ai-tika-document-reader、spring-ai-pdf-document-reader、自定义文本清洗/预处理工具
- 向量嵌入:Ollama 本地模型(BGE-M3、nomic-embed-text 均可)
- 向量数据库:Redis Stack(轻量、易部署、支持向量检索,适配中小规模企业场景)
- 大模型:Ollama 本地大模型(ministral-3:8b)
二、核心技术模块实现
2.1 向量与嵌入基础
2.1.1 Embedding 定义与作用
Embedding(嵌入)是将非结构化数据(文本、图像、视频等)转换为结构化的浮点数数组(向量)的过程。其中,文本 Embedding 是 RAG 技术的核心,其核心作用是:
- 将文本的语义信息转化为可计算的向量,实现「语义相似度匹配」(而非字面匹配);
- 将文本向量存入向量数据库,实现高效的相似性检索,快速找到与用户提问最相关的知识片段;
- 解决大模型无法直接处理长文本、私有文本的问题,为生成式 AI 提供精准的知识支撑。
2.1.2 Embedding 模型使用(本地 Ollama 版)
本方案采用 Ollama 本地运行 Embedding 模型,无需联网,适配本地学习和企业内网部署:
- nomic-embed-text:向量维度 768,轻量(60MB),CPU 运行速度快(比 BGE-M3 快 20 倍),适合本地学习、CPU 环境及中小规模知识库场景。
Spring AI 自动对接 Ollama Embedding 模型,无需手动实现向量生成逻辑,只需配置模型名称即可。
2.2 文本分块、清洗与预处理
文本预处理是 RAG 检索精度的核心保障——未经处理的文本(乱码、空格、无效信息)会导致向量生成失真,检索准确率下降。本方案实现完整的文本处理链路:PDF 解析 → 文本清洗 → 预处理 → 智能分块。
2.2.1 依赖引入(PDF 解析+文本处理)

2.2.2 核心工具类实现
(1)PDF 解析工具类(PdfParser)
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.IOException;
@Component
public class PdfParser {
/**
* PDF 文件字节数组 → 提取纯文本
* @param pdfBytes PDF 文件字节流(前端上传后获取)
* @return 提取的纯文本
* @throws IOException 解析异常
*/
public String extractText(byte[] pdfBytes) throws IOException {
// 自动关闭文档流,避免资源泄露
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
PDFTextStripper stripper = new PDFTextStripper();
// 按 PDF 页码顺序提取所有文本
return stripper.getText(document);
}
}
}
(2)文本清洗与预处理工具类(TextProcessor)
import org.springframework.ai.document.Document;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
@Component
public class TextProcessor {
// 智能分块器(递归分块算法,Spring AI 默认最优算法)
private static final TokenTextSplitter SPLITTER = new TokenTextSplitter(
800, // chunkSize:每块最大 token 数(1 token ≈ 0.7-0.8 中文汉字)
200, // minChunkSizeChars:每块最小字符数(过滤碎片)
10, // minChunkLengthToEmbed:允许向量化的最小字符数(过滤无效内容)
10000, // maxNumChunks:最大分块数(防止超大文本撑爆系统)
true // keepSeparator:保留分隔符(保持段落语义完整)
);
/**
* 文本清洗:移除无效信息,标准化文本格式
*/
public String cleanText(String text) {
if (text == null) return "";
// 1. 替换多余空格、制表符、全角空格
text = text.replaceAll("\\s+", " ");
text = text.replaceAll(" ", " ");
// 2. 移除多余空行(连续换行保留1个)
text = text.replaceAll("\\n+", "\n");
// 3. 移除 PDF 常见垃圾信息(页码、页眉页脚)
text = text.replaceAll("^\\d+$", "");
text = text.replaceAll("第\\d+页", "");
text = text.replaceAll("Page \\d+", "");
// 4. 移除特殊符号、零宽字符、乱码
text = text.replaceAll("[\\u200B\\u00A0]", "");
text = text.replaceAll("[^\\p{Print}\\p{Han}]", "");
// 5. 清理 URL、邮箱(可选,根据业务需求调整)
text = text.replaceAll("https?://\\S+", "");
text = text.replaceAll("\\S+@\\S+", "");
return text.trim();
}
/**
* 文本预处理:标准化格式,强化语义
*/
public String preprocess(String text) {
text = cleanText(text);
// 中文标点标准化(避免中英文标点混用导致语义失真)
text = text.replaceAll("\\. ", "。");
text = text.replaceAll(", ", ",");
text = text.replaceAll("\\? ", "?");
text = text.replaceAll("! ", "!");
// 标题强化(换行分隔,提升分块后检索精度)
text = text.replaceAll("第[一二三四五六七八九十]+章", "\n$0");
text = text.replaceAll("第\\d+章", "\n$0");
return text.trim();
}
/**
* 智能分块:清洗预处理后,生成可向量化的文档块
* @param rawText 原始文本(PDF 解析后)
* @param source 文档来源(如:纯休息休息吧.pdf,用于后续块管理)
* @return 分块后的文档列表(每个块包含文本和元数据)
*/
public List<Document> splitDocuments(String rawText, String source) {
if (rawText == null || rawText.isBlank()) {
return new ArrayList<>();
}
// 1. 文本清洗 + 预处理
String processedText = preprocess(rawText);
// 2. 构建原始文档,存入元数据(source 用于后续按文件管理块)
Document originalDoc = new Document(processedText);
originalDoc.getMetadata().put("source", source);
originalDoc.getMetadata().put("textLength", processedText.length());
// 3. 递归分块(按优先级切割:段落→换行→句号→逗号→硬切,保持语义完整)
List<Document> chunks = SPLITTER.apply(List.of(originalDoc));
// 4. 给每个分块打上 source 标签,便于后续按文件查询、删除
for (Document chunk : chunks) {
chunk.getMetadata().put("source", source);
}
return chunks;
}
}
2.3 向量数据库(Redis Stack)
本方案选用 Redis Stack 作为向量数据库,其优势在于:轻量易部署、支持向量检索、与 Java 生态适配性好、适合中小规模知识库(企业 90% 基础场景够用),无需复杂的数据库配置,本地学习和企业部署均可快速上手。具体关于Redis Stack 的知识网上自行搜索。
2.3.1 application.yml配置
spring:
ai:
ollama:
base-url: http://localhost:11434 # Ollama 本地服务地址
embedding:
model: nomic-embed-text # 选用轻量 CPU 友好型 Embedding 模型
chat:
model: llama3 # 本地对话大模型(可替换为其他 Ollama 支持的模型)
data:
redis:
vector-store:
host: localhost # Redis 地址(本地部署)
port: 6379 # Redis 端口
index-name: rag-index # 向量索引名称(自定义)
dimension: 768 # 向量维度(与 Embedding 模型一致:nomic-embed-text 768维)
2.3.2 向量数据库核心操作
Spring AI 封装了 Redis 向量库的所有操作,无需手动编写 Redis 命令,核心操作如下(集成在 RAG Service 中):
- 向量入库:将分块后的文档向量存入 Redis;
- 相似性检索:根据用户提问的向量,检索出最相关的文档块;
- 全量查询:查询 Redis 中所有文档块(用于本地调试)。
2.3.3 向量数据效果


三、完整 RAG 流程实现
完整 RAG 流程分为「文档入库链路」和「用户提问链路」,两条链路相互独立,可单独扩展,以下是核心 Service 实现(整合所有模块)。
3.1 核心 Service 实现(RagService)
package org.example.testai.service.businessService;
import org.apache.commons.lang3.time.StopWatch;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.document.Document;
import org.springframework.ai.embedding.EmbeddingModel;
import org.springframework.ai.embedding.EmbeddingResponse;
import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.ollama.OllamaEmbeddingModel;
import org.springframework.ai.reader.ExtractedTextFormatter;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.reader.pdf.config.PdfDocumentReaderConfig;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.ai.vectorstore.filter.Filter;
import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder;
import org.springframework.ai.vectorstore.redis.RedisVectorStore;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import redis.clients.jedis.JedisPooled;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@Service
public class RagService {
private final OllamaChatModel chatModel;
private final ChatClient chatClient;
private final VectorStore vectorStore;
private final TokenTextSplitter textSplitter;
// private final EmbeddingModel embeddingModel;
// 统一分块配置(生产标准:512token,100token重叠)
public RagService(VectorStore vectorStore, OllamaChatModel chatModel, ChatClient chatClient) {
this.vectorStore = vectorStore;
this.chatModel = chatModel;
this.chatClient = chatClient;
this.textSplitter = new TokenTextSplitter();
}
// ====================== 生产级:文件加载 ======================
public void loadDocumentFromFile(MultipartFile file) throws Exception {
String fileName = file.getOriginalFilename();
// 1. 格式校验(生产必做)
if (!isValidFile(fileName)) {
throw new IllegalArgumentException("不支持的文件格式,仅支持:pdf、docx、txt、md");
}
// 2. 多格式解析(Spring AI 原生支持)
List<Document> documents;
if (fileName.endsWith(".pdf")) {
// PDF 专用解析(支持分页、OCR)
PdfDocumentReaderConfig config = PdfDocumentReaderConfig.builder()
.withPageExtractedTextFormatter(new ExtractedTextFormatter.Builder()
.withNumberOfBottomTextLinesToDelete(3)
.build())
.build();
PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(file.getResource(), config);
documents = pdfReader.read();
} else {
// Tika 通用解析(支持Word/Excel/Markdown等所有格式)
TikaDocumentReader tikaReader = new TikaDocumentReader(file.getResource());
documents = tikaReader.read();
}
// 3. 文本清洗 + 分块 + 入库
processAndStoreDocuments(documents, fileName);
}
// ====================== 测试用:文本加载(生产可关闭) ======================
public void loadDocumentFromText(String content) {
Document doc = new Document(content);
processAndStoreDocuments(List.of(doc), "测试文本");
}
// ====================== 统一处理逻辑 ======================
private void processAndStoreDocuments(List<Document> rawDocs, String source) {
// 1. 文本清洗(生产必做:去噪、去乱码、归一化),可以添加更多操作
List<Document> cleanedDocs = rawDocs.stream()
.peek(doc -> doc.getMetadata().put("source", source)) // 记录来源,生产必加
.toList();
// 2. 语义分块
List<Document> chunks = textSplitter.apply(cleanedDocs);
// 3. 向量化入库
vectorStore.add(chunks);
System.out.printf("✅ 文档「%s」处理完成,分块数:%d%n", source, chunks.size());
}
// ====================== 格式校验 ======================
/*
* 此处省略更多格式,只做演示
* */
private boolean isValidFile(String fileName) {
if (fileName == null) return false;
String lowerName = fileName.toLowerCase();
return lowerName.endsWith(".pdf") || lowerName.endsWith(".docx")
|| lowerName.endsWith(".txt") || lowerName.endsWith(".md");
}
public String ask(String question) {
StopWatch sw = new StopWatch();
sw.start();
List<Document> docs = vectorStore.similaritySearch(question);
sw.split();
System.out.printf("相似度搜索耗时:%d 秒%n%n", sw.getSplitTime()/1000);
StringBuilder context = new StringBuilder();
docs.forEach(doc -> context.append(doc.getText()).append("\n\n"));
String prompt = """
你是企业智能助手,严格参考以下资料回答,禁止编造。
资料来源:%s
问题:%s
""".formatted(context, question);
// String call = chatModel.call(prompt);
String call = chatClient.prompt(prompt).call().content();
sw.stop();
System.out.printf("总耗时:%d 秒%n", sw.getTime(TimeUnit.SECONDS));
return call;
}
/*
* 想要自定义条数,默认用的是4条
* */
public List getAll(String msg) {
SearchRequest request = SearchRequest.builder()
.query(msg)
.topK(5)
.build();
List<Document> documents = vectorStore.similaritySearch(request);
System.out.println(documents);
return documents;
}
}
3.2 流程说明
3.2.1 文档入库流程(核心链路)
- 用户接口上传文档文件(字节流);
- 工具列解析提取文件中的文本;
- 通过 TextProcessor 对文本进行清洗、预处理(去垃圾信息、标准化格式);
- 通过 TokenTextSplitter 进行智能递归分块,生成多个语义完整的文档块;
- Spring AI 自动调用 Embedding 模型,将每个文档块转换为向量;
- 将向量及文档块元数据(source 等)存入 Redis 向量库。
3.2.2 用户提问流程(核心链路)
- 用户输入提问(如:“某某某.pdf 中提到的 xxx 有哪些?”);
- Spring AI 自动调用 Embedding 模型,将提问转换为向量;
- 向量数据库(Redis)根据提问向量,检索出最相关的 N 个文档块(topK 可配置,默认是 4);
- 拼接检索到的文档块,构造提示词(限制大模型仅参考该内容);
- 调用本地大模型(如 llama3),生成有依据、无幻觉的回答;
- 将回答返回给用户。
https://gitee.com/an-ape-hou/spring-ai
https://gitee.com/an-ape-hou/spring-ai 具体实现代码查看代码库
&spm=1001.2101.3001.5002&articleId=160023666&d=1&t=3&u=00722d63ff9943d49915fdabebf26935)
1390

被折叠的 条评论
为什么被折叠?



