ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Spring AI + Ollama 本地搭建 RAG:从文档切分到知识库问答实战

Spring AI + Ollama 本地搭建 RAG:从文档切分到知识库问答实战 上个月我接了个内部需求把公司那本 80 页的产品手册喂给大模型做成一个能回答员工提问的问答机器人。我一开始想得很简单把手册全文塞进 Prompt 就行。结果 40 多页读进去上下文窗口直接报警模型越到后面越像失忆开始拿通用知识忽悠人。后来我换成 RAG检索增强生成方案用 Spring AI 做了一套完整的“文档觉醒”流程才真正体会到这件事的边界在哪里。这篇是第 4 集前几集写的是模型接入和提示词玩法这一集专门讲 RAG 从零落地本地跑通、代码可复现、坑都踩给你看。整个过程全程用 Ollama 本地模型不需要外部 API Key数据不出内网适合刚接触 RAG 的 Java 工程师也适合想快速给业务方演示“AI 能回答私有文档问题”的小伙伴。我会把索引、检索、问答三段完整拆开讲清楚每一步为什么这么做以及我第一次实测时是怎么翻车、怎么排查的。1. 为什么非让AI“看文档”不行大模型的记忆边界与私有知识盲区1.1 大模型的知识边界在哪里大模型聊天再厉害它的知识也停留在训练数据截止的那一刻。你问它“2025 年发布的某设备怎么配置”它大概率会一本正经地编一个答案。原因很简单它没见过你的产品手册、你的内部流程、你的历史项目文档这些东西根本不在它的记忆里。我习惯把大模型比作“通识课毕业生”而不是“你们公司员工手册考试通过者”。它懂语言、懂逻辑、懂常识但唯独不懂你塞在共享盘里的那些私有资料。想让它懂传统上有两条路微调和 RAG。微调是让模型“记住”你的数据分布RAG 是让模型在回答时“临时翻书”。对大多数业务场景RAG 的性价比明显更高。1.2 为什么先选 RAG 而不是微调很多人一听“让 AI 学会看文档”第一反应是微调。但实际上微调是给模型“换脑子”需要高质量标注数据、GPU 资源、以及反复的实验RAG 是给模型“配一本查阅手册”只需要把文档清洗、切块、建索引。下面这张表我每次做技术选型都会拿出来比一遍对比维度微调RAG数据需求需要成百上千对高质量问答样本有原始文档即可清洗成本低知识更新更新一次要重新训练重新跑一遍索引即可幻觉控制仍然会编造且无法溯源可以引用原始文档片段可解释实施门槛需要 GPU 资源、训练经验普通 Java 工程就能跑回答速度推理速度受模型规模影响多一步检索但整体可控微调不是没用但在“已有文档、需要基于文档回答”这种场景下RAG 几乎是更稳的第一步。尤其当你手里是一堆持续更新的产品文档时RAG 的“改文档重新入库”优势是微调完全比不了的。1.3 RAG 到底适合解决什么问题我做完第一个 RAG 原型之后体会最深的一点是它最适合“从已有资料中找答案”的知识密集型任务。典型场景包括产品手册、操作文档的智能客服用户问“怎么重置密码”系统从手册里找到对应章节回答企业内部制度、流程问答比如“报销额度超过 5000 要走什么审批”技术方案评审辅助从过去的历史方案文档里检索经验教训代码仓库问答把 README、设计文档、关键模块注释切片后提供检索但也要说清楚RAG 不适合需要多步数学推理、严格逻辑推导的任务。比如让 AI“基于文档数据算一下季度增长率并对比三年趋势”它就算检索到了数据也可能算错。这是生成模型本身的能力边界不是 RAG 能全包的。2. RAG链路拆解切分、向量化与召回搞懂原理才能合理调参2.1 两阶段运行模型先“上架图书”再“查目录取书”RAG 全流程可以拆成两段一段离线、一段在线。离线阶段叫索引构建把文档加载进来切成一段一段的小块用向量模型把每一块“翻译”成一组数字然后存进向量库。在线阶段叫查询问答把用户问题也“翻译”成数字去向量库里找最接近的几块文本把这些文本拼到一个提示词里最后交给大模型生成回答。我常用一个图书馆类比离线阶段是“给新书编目上架”每一页都做了标签在线阶段是“用户报一个问题图书管理员先去目录里找最相关的几页再把这几页递给一位专家专家根据这几页给出回答”。如果你把整本书直接扔给专家他翻不过来如果图书管理员找错了页专家再厉害也答不准。后面所有调参本质上都是在调“图书管理员”的找书水平。2.2 文档切分为什么不能把整篇文档直接向量化很多人第一次做 RAG会试图把整篇 PDF 做一次向量化再存进去。结果检索时要么匹配模糊要么把大段的无关内容一起塞给模型。根本原因在于一段文字越长它的向量就越“平均”丢失了局部语义。比如整本手册的向量可能接近“公司产品介绍”而你问的是“重置密码”相关细节早就被平均掉了。所以必须先切块也就是 Chunking。Spring AI 里我常用TokenTextSplitter它按 token 数切分而不是按字符数。核心参数有两个chunkSize每一块包含多少 token决定语义粒度chunkOverlap相邻两块之间重叠多少 token保证跨块上下文不丢切块太小答案会被拆散模型看到的是残缺步骤切块太大噪声变多检索命中率下降。300 个 token 加 50 个 token 重叠是我比较推荐的起点但绝对不是标准答案。文档句式、术语密度都影响最优值后面第 6 章会用实测数据说明。这里顺带回应一个热词很多人问“有没有本地的 RAG 文本拆解工具”。TokenTextSplitter就是纯本地执行的不调用外部 API对数据敏感的场景特别友好。2.3 向量化与向量库把文字变成可计算的距离切块之后每一块文本需要交给 Embedding 模型生成向量。向量是什么简单理解就是一组浮点数比如 768 维或 1024 维语义越接近的文本向量方向越接近。你问“怎么改密码”和文档里“重置管理员口令”这段字面上完全不同但向量空间里挨得很近这就是语义检索的价值。向量库负责存储这些向量并提供相似度搜索。Spring AI 里有几个选择向量库特点适合阶段SimpleVectorStore内存实现零配置入门学习、Demo 演示Chroma本地文件持久化轻量小团队、本地原型PgVectorPostgres 扩展复用现有库生产环境常见选择Milvus / Redis分布式或复用缓存体系大规模、高并发我建议入门阶段无脑用SimpleVectorStore因为它的依赖最少跑通流程最重要。但你要知道它是纯内存的重启就没了生产别用它。2.4 检索召回与生成RAG 的上限由召回决定检索阶段最核心的操作是相似度搜索。Spring AI 里通过VectorStore.similaritySearch完成你可以指定topK也就是返回最相似的几块文档。这一步决定了模型“看”什么。模型最后生成的质量高度依赖召回质量——如果答案所在的那块文档根本没被找回来模型就只能瞎编。这里有个很容易被忽略的观点RAG 的成功率上限其实是召回率决定的。生成模型只要没被超出上下文窗口基本能把召回的内容复述明白真正让回答跑偏的往往不是模型不会答而是检索阶段压根没把正确答案捞上来。所以我每次排查问题第一步永远是看“召回了什么”而不是急着调 Prompt。这也是后文排查记录的核心思路。3. 实测环境准备Ollama拉模型 Spring Boot工程初始化一步不能少3.1 安装 Ollama 并拉取对话模型、向量模型先说模型层。为了不依赖外部 API Key我把 ChatGPT 这类云端接口放一边直接用 Ollama 在本地跑开源模型。Ollama 的安装很简单官网下载对应系统的安装包即可macOS、Linux、Windows 都有客户端。装好之后拉两个模型一个负责对话生成一个负责向量化ollama pull qwen2.5:7b ollama pull nomic-embed-textqwen2.5:7b是对话模型7B 参数16G 内存的机器跑起来比较舒服如果机器只有 8G 内存建议换qwen2.5:3b损失一点回答质量但更流畅。nomic-embed-text是向量模型几百 MB专门用来把文本变成向量。验证是否装好执行ollama list能看到这两个模型就说明没问题。你也可以直接调 API 试一下curl http://localhost:11434/api/tags能返回 JSON 模型列表就说明 Ollama 服务起来了。3.2 创建 Spring Boot 工程和 Spring AI 依赖Spring AI 已经出了 1.0 GA版本迭代很快。我当前的工程结构是 JDK 17 Spring Boot 3.x Spring AI BOM 1.0.0。Maven 的pom.xml核心部分如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.5.3/version relativePath/ /parent properties java.version17/java.version spring-ai.version1.0.0/spring-ai.version /properties dependencyManagement dependencies dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-bom/artifactId version${spring-ai.version}/version typepom/type scopeimport/scope /dependency /dependencies /dependencyManagement dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-starter-model-ollama/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-pdf-document-reader/artifactId /dependency dependency groupIdorg.springframework.ai/groupId artifactIdspring-ai-vector-store-simple/artifactId /dependency /dependencies其中spring-ai-starter-model-ollama负责自动装配 Ollama 的客户端、ChatModel 和 EmbeddingModelspring-ai-pdf-document-reader负责解析 PDFspring-ai-vector-store-simple提供内存向量库实现。3.3 application.yml 配置版本差异是这里最大的坑接下来是最容易踩坑的配置文件。Spring AI 版本迭代快Ollama 相关属性名改过好几次。我当前跑通的配置如下spring: ai: ollama: base-url: http://localhost:11434 chat: model: qwen2.5:7b embedding: model: nomic-embed-textbase-url不写也有默认值但我建议显式写出来。chat.model指对话模型embedding.model指向量模型这两个配置必须和你在 Ollama 里拉取的模型名称严格一致哪怕少一个 tag 后缀都会报模型找不到。注意如果你用的是 Spring AI 1.0 之前的版本属性名可能是spring.ai.ollama.chat.options.model。如果你的 IDE 没有弹出对应属性的自动提示多半是版本里 key 变了。我会建议你在配置类里看一眼自动配置的属性前缀或者直接用启动日志验证最终用的模型名而不是死记配置 key。3.4 验证模型链路是否连通配置完了别急着写业务代码先做一次最基础的连通性测试。写一个CommandLineRunner启动时直接调一次对话模型Component public class ModelCheckRunner implements CommandLineRunner { private final ChatModel chatModel; public ModelCheckRunner(ChatModel chatModel) { this.chatModel chatModel; } Override public void run(String... args) { String response chatModel.call(请回复连接正常四个字); System.out.println(对话模型返回: response); } }如果启动后看到“连接正常”说明 ChatModel 通了。Embedding 模型暂时不用单独验证等到索引构建时自然会用上。如果报错说 model not found大概率是ollama pull没执行成功或配置里的模型名和ollama list不一致。4. 索引构建实战文档加载、TokenTextSplitter 切分与向量库入库4.1 文档加载从 PDF 到可处理的 Document 列表索引构建的第一步是读文档。Spring AI 里的PagePdfDocumentReader可以把 PDF 按页读成一个个Document对象。Document是 Spring AI 的统一文档模型里面有文本内容还可以带元数据。我把测试用的《智能考勤一体机操作手册.pdf》放到了src/main/resources/docs/目录下加载代码很简单PagePdfDocumentReader reader new PagePdfDocumentReader( new ClassPathResource(docs/manual.pdf)); ListDocument documents reader.read();这个 reader 对文本型 PDF 很好用但如果是扫描件本质是图片它就无能为力了。你需要先 OCR 把图片转成文字再做后续处理。顺带回应一个热词“RAG 知识库能存储图片吗”文本向量库本身不存图片语义如果文档里有图表信息要么用 OCR 把图片里的文字抽出来要么走多模态 Embedding普通文本 RAG 是不直接处理图片的。如果你要加载的不是 PDF 而是 Markdown 或纯文本Spring AI 也有对应的TextReader把文件路径传进去就能读。思路完全一样都是先变成Document列表。4.2 TokenTextSplitter 参数300 和 50 的含义文档读进来之后直接整页存进向量库是常见错误。一页 PDF 往往超过 500 个 token语义跨度也可能很大例如一页既有功能介绍又有参数表。我用TokenTextSplitter做切分TokenTextSplitter splitter TokenTextSplitter.builder() .withChunkSize(300) .withChunkOverlap(50) .build(); ListDocument chunks splitter.apply(documents);chunkSize300表示目标每个文档块约 300 个 token。我选这个值的理由有两个第一300 个 token 能覆盖“一个操作步骤 几句说明”这样相对完整的信息单元第二这个体量塞进后续 Prompt 时3~5 个块加起来不会超出常见模型的上下文窗口。chunkOverlap50表示相邻块共享 50 个 token避免一句话被拦腰切断后下一块开头接不上。如果你问我“这套参数通用吗”我会说不通用。中文文档里术语密度高、表格多最优参数往往要试 2~3 轮。第一次先用 300/50 跑通全流程再根据检索效果调整这是最务实的路径。4.3 选择 SimpleVectorStore 入门阶段的耐心之选向量库我这里选SimpleVectorStore配置方式极其简单VectorStore vectorStore SimpleVectorStore.builder(embeddingModel).build();它把向量放在内存里不需要额外安装数据库也不需要配置连接信息。缺点前面说过进程重启数据就没了。但入门阶段这正是优点没有外部依赖出问题一定出在自己的代码或模型配置上排查范围小。Spring AI 的设计是向量库不感知你用的是哪种 Embedding 模型它只负责把Document里的内容用传入的embeddingModel转成向量再存起来。所以构建VectorStore时一定要传入同一个EmbeddingModelbean。4.4 完整的索引构建代码我把索引构建放在一个RagService里后续问答也复用这个类。为了不被 import 干扰思路下面的代码我故意省略 importIDE 里用自动导入即可Service public class RagService { private final VectorStore vectorStore; private final ChatModel chatModel; public RagService(EmbeddingModel embeddingModel, ChatModel chatModel) { this.vectorStore SimpleVectorStore.builder(embeddingModel).build(); this.chatModel chatModel; } public void buildIndex() { PagePdfDocumentReader reader new PagePdfDocumentReader( new ClassPathResource(docs/manual.pdf)); ListDocument documents reader.read(); TokenTextSplitter splitter TokenTextSplitter.builder() .withChunkSize(300) .withChunkOverlap(50) .build(); ListDocument chunks splitter.apply(documents); vectorStore.add(chunks); System.out.println(索引完成chunk 总数: chunks.size()); } }这里省略了所有 import因为 Spring AI 不同版本的包名确实动过几次尤其是TokenTextSplitter从transformer.splitter到document.splitter都出现过。我用 IDE 的自动导入能少走弯路也建议你这样做。vectorStore.add(chunks)这一步会依次调用 EmbeddingModel 为每个 chunk 生成向量。nomic-embed-text是本地模型速度很快一篇 20 页的 PDF 几秒钟就能完成索引。4.5 用一个 Controller 暴露操作入口为了方便测试我加了一个 Controller用 HTTP 接口触发索引构建和后续的问答RestController RequestMapping(/rag) public class RagController { private final RagService ragService; public RagController(RagService ragService) { this.ragService ragService; } PostMapping(/build) public String build() { ragService.buildIndex(); return 索引构建完成; } PostMapping(/ask) public String ask(RequestBody MapString, String body) { return ragService.ask(body.get(question)); } }启动 Spring Boot 后执行curl -X POST http://localhost:8080/rag/build看到“索引构建完成”说明文档已经切块并入库在线问答阶段可以开始了。5. 问答阶段实战相似度搜索、上下文拼装与QuestionAnswerAdvisor5.1 检索怎么把问题变成“查文档”的动作问答阶段的第一个动作是检索。Spring AI 的VectorStore提供了similaritySearch传入用户问题就能返回最相似的文档块。我习惯用SearchRequest显式指定topKListDocument matches vectorStore.similaritySearch( SearchRequest.builder(question) .withTopK(3) .build());topK3表示返回最相似的 3 个文档块。这个值不是越大越好太大模型会被无关噪声干扰太小正确答案可能漏掉。我建议先从 3 开始后面根据命中情况调整。检索结果里每个Document带有一个分数表示向量相似度。通常超过 0.7 才算比较相关但不同 Embedding 模型的分数分布差别很大不要直接套用网上的阈值先打印几次结果看看自己的分数长什么样。5.2 上下文拼装提示词里一定要堵住“编造”的口子检索完下一步是把召回的文档块拼进提示词。这里有一个我每次都必须做的动作在提示词里明确禁止编造。因为大模型天生有“流畅地胡扯”的倾向尤其当答案不在检索内容里时它会试图用训练知识补全。下面是经过几次教训后我固定下来的 Prompt 模板String context matches.stream() .map(Document::getText) .collect(Collectors.joining(\n\n---\n\n)); String prompt 你是一个产品手册问答助手。 请严格根据下面的资料回答用户问题。 资料里没有的内容直接回答“资料中没有找到”不要编造。 资料 %s 用户问题 %s .formatted(context, question); String answer chatModel.call(prompt);这段代码里最关键的句子是“资料里没有的内容直接回答‘资料中没有找到’”。实测下来它能明显减少幻觉但并不能完全消除。如果召回的文档块本身包含错误信息模型也会一本正经地复述错误这是 RAG 的固有风险只能靠提升召回质量来缓解。5.3 QuestionAnswerAdvisorSpring AI 自带的 RAG 顾问如果你觉得手拼 Prompt 有点繁琐Spring AI 还提供了一个现成组件QuestionAnswerAdvisor。它的作用相当于是把“检索 拼上下文 问答”封装成了一个顾问对象配合ChatModel直接使用QuestionAnswerAdvisor advisor new QuestionAnswerAdvisor(vectorStore); String answer chatModel.call( new Prompt(question, advisor) ).getResult().getOutput().getText();QuestionAnswerAdvisor会自动把检索到的文档注入 Prompt你不需要手动拼资料段。我个人建议第一次做 RAG还是先手写 Prompt 一遍把检索结果和最终回答的前因后果看清楚跑通了再换顾问组件这样你对链路里每一步发生的“魔法”都有体感。除了QuestionAnswerAdvisorSpring AI 还有QueryTransformer和QueryExpander这类进阶组件可以把用户问题改写得更适合检索。不过初学者先不用碰等基础链路稳定了再考虑。5.4 接口测试用 curl 直接验证问答效果索引构建完成后直接调问答接口curl -X POST http://localhost:8080/rag/ask \ -H Content-Type: application/json \ -d {question: 如何重置管理员密码}正常情况下模型会从手册检索到的段落里提取步骤回答。如果它回答“资料中没有找到”别慌——这是提示词在起作用说明检索到的文档块里确实没有直接答案。这时候要做的不是怀疑模型而是去看第 6 章的排错套路。5.5 这一步最容易踩的三个坑第一个坑对话模型没配置对。很多人启动后没验证就开问结果 Ollama 默认用了没有拉取的模型或者配置 key 不匹配报 404。所以第 3 章的连通性验证真的别跳过。第二个坑Embedding 模型和向量库不一致。比如索引阶段用nomic-embed-text后来换成了别的模型旧向量和新向量空间不一致检索分数会变得毫无意义。换模型必须重新构建索引。第三个坑上下文长度估算错误。如果把topK调到 5每块又 500 token光资料就 2500 token加上问题和其他系统提示词很容易逼近小上下文模型的限制。建议切分时用 300 token 左右topK控制在 3~5给模型留足生成空间。6. 第一次跑RAG就翻车召回质量、切块参数与相似度阈值的完整排查6.1 我的测试场景和三个问题演示环境里我放了一份 20 页的《智能考勤一体机操作手册》里面有管理员密码重置、考勤方式、数据导出三大部分。我准备了三个问题“如何重置管理员密码”“支持哪些考勤方式”“数据导出支持哪些格式”这三个问题覆盖了“单一知识点”“分散知识点”“表格型信息”三种情况很适合用来暴露 RAG 的不同问题。6.2 翻车现象有的漏步骤有的胡说有的答非所问我用最朴素的参数chunk_size500overlap50topK1跑了一轮结果非常打脸问题 1 的回答只说了“进入系统管理找到管理员设置”但漏掉了“恢复出厂设置后的首次登录仍默认 admin123”这一步问题 2 直接把模型训练记忆里的内容搬出来了说支持“钉钉打卡、微信打卡”手册里写的其实是刷卡、人脸、指纹问题 3 更离谱问的是导出格式它回答了一堆导出操作步骤最后也没说清支持 CSV 还是 Excel这三个问题本质上都是同一个毛病模型没看到真正包含答案的文档块。6.3 排查链路先看召回再查切块我的排查习惯是先打印召回结果而不是急着调 Prompt。于是在ask方法里临时加了段输出把每个召回块的前 100 个字符和相似度分数打出来for (Document match : matches) { System.out.println(score match.getScore() , 内容开头 match.getText().substring(0, Math.min(100, match.getText().length()))); }日志一出来问题就清楚了问题 1 的召回块是“登录界面说明”不是“管理员密码重置”答案当然缺步骤问题 2 的召回块里只有一行提到了“刷卡”其他都是打卡设备的硬件参数模型被噪声带偏问题 3 的召回块是“数据导出操作前准备”而真正的“支持格式”表格在另一块文档里因为 chunk 太大被平均掉了接着查切块把 500 token 改成更小粒度后发现“管理员密码重置”的步骤分布在两个相邻 chunkoverlap 50 不够中间一句话被切断。这就是为什么 300/80 的组合在实测里更稳chunk 小到能保留局部细节overlap 大到能承接跨块语义。6.4 参数对比同一份文档不同切分检索配置的命中差异我花了一个下午做了组简单的参数对比。命中判定是“召回的前几个 chunk 里是否包含完整答案”。注意这是我那一份 20 页文档、三个问题上的结果不是通用基准但趋势很有参考价值chunk_sizeoverlaptopK问题1 密码重置问题2 考勤方式问题3 导出格式整体感受500501漏步骤答非所问错误召回太粗噪声大300503部分命中幻觉减少但仍有错勉强命中初步可用缺上下文200805完整命中正确但夹杂噪声完整命中召回率高上下文偏碎300803完整命中正确正确当前文档的最优解最终我采用的是chunk_size300、overlap80、topK3。我特别想强调这个组合只对我这份操作手册最优。如果你的文档是长篇小说或者论文最优参数一定会变。但排查流程是通用的先打印召回再调切分最后调 topK。6.5 复盘真正的瓶颈在召回和数据整理这次翻车让我彻底接受了开头那个判断RAG 的瓶颈很少在模型生成而在召回和数据整理。文档切得太碎关键步骤被拦腰截断切得太粗关键信息被噪声稀释topK 太小正确答案根本进不了 Prompt。这些都是数据工程问题不是模型理解力问题。另外我意识到“知识割裂”是比参数更隐蔽的坑。比如“导出格式”这个答案在文档的表格里而“导出步骤”在正文里它们被分到了不同的 chunk。只靠向量相似度模型很难同时把两段知识拼起来。这种问题靠调参解决不了得靠第 7 章里的重排序、混合检索甚至 GraphRAG 的路径。7. 从朴素RAG往前走重排序、Agentic RAG与GraphRAG的工程化路线7.1 朴素 RAG 的瓶颈到底在哪里把基础链路跑通之后你很快会碰到朴素 RAG 的天花板。我总结下来有三类最明显的瓶颈第一召回不全。向量相似度擅长“语义相近”但不擅长“术语精确匹配”。比如文档里写的是“口令”用户问的是“密码”语义模型能兜住但如果是设备型号“XA-200”用户问“XA200”很多向量模型就匹配不上。这时候需要加关键词检索。第二碎片化。答案跨多个 chunk 时模型需要跳着读简单拼接 Prompt 很容易丢失逻辑关系。这也是知识割裂的来源。第三多跳推理弱。问“某功能影响了哪些模块”答案分散在三页不同章节朴素 RAG 只能各自召回没法串联。这类问题靠加大 topK 也不行因为噪声会同步增长。7.2 改进第一步重排序与混合检索我建议工程上第一个升级动作是加重排序Rerank和混合检索。思路是先用向量检索召回 20 个候选块再用一个 Rerank 模型精排取前 3 个最相关的块给大模型。这样既保住了召回率又控制了噪声。混合检索则是把向量检索和 BM25 关键词检索的结果做合并。Spring AI 周边生态里已经有这类实现LangChain4j 等 Java 框架也提供了类似能力。它特别适合文档里充满编号、型号、专有名词的场景。如果你还没扛到那一步至少可以在相似度检索之外加一层元数据过滤比如按“章节”或“文档类型”限定候选范围。7.3 Agentic RAG让模型自己决定怎么查再往前走一步就是热词里反复出现的 Agentic RAG。朴素 RAG 是“查一次答一次”Agentic RAG 是让模型像一个有工具的人先分析问题决定要不要检索、检索哪类知识、要不要追问澄清甚至根据第一次检索结果决定是否再查一次。举个例子用户问“怎么导出考勤数据”Agent 会先意识到“考勤数据导出”可能涉及权限、格式、周期三个子问题于是先查“导出权限”再查“导出格式”最后结合两次结果给出完整回答。代价是代码复杂度和延迟都上升不是所有场景都值得。Spring AI 的 advisor 机制和 ChatClient 给了基础支持但真要做到生产可用的 Agentic RAG你还是得自己做规划、工具调用、记忆管理那一套。7.4 GraphRAG 与本体 RAG治“知识割裂”的重型武器热词里还有 GraphRAG 和 ontology RAG。它们解决的核心问题正是前面说的“知识割裂”。GraphRAG 会把文档里的实体比如某个功能、某个硬件和关系“依赖”“包含”“影响”抽取出来构建成一张知识图谱。回答跨文档问题时模型可以先在图谱上跳转把相关实体串起来再去定位具体文档块。本体 RAG 更进一步先用领域本体定义好概念关系再让切分和检索服从这套结构。比如考勤系统的本体里定义了“管理员”“考勤方式”“数据导出”这些概念索引阶段就把文档块打上本体标签检索阶段可以按概念过滤。效果好是真好但建本体、做抽取都是不小的前置成本。小项目一开始就上 GraphRAG大概率是在给自己找麻烦。7.5 用数据说话怎么评估 RAG 到底变好没有很多人调参全凭感觉我强烈建议你准备一套小评测集。30~50 条问答就够了每条标注出“正确答案在哪几段文档里”。然后跑两个指标Hit Rate回答的问题里检索返回的前 K 个文档块中有多少包含了答案所在段落Context Relevance召回的上下文与问题的相关程度可以人工打分也可以用模型粗评每次改切分参数、topK、加不加重排序都先在这套评测集上测一遍用数字说话。我自己做 RAG 调优时流程永远是跑评测、看失败案例、改参数、再跑评测。而不是盯着某一个成功案例就以为大功告成那样只会顾此失彼。我自己的体会是RAG 不是什么高深莫测的东西它本质上就是把“搜索引擎”接到“大模型”前面的一套系统工程。刚开始做别急着追 Agentic、GraphRAG 这些时髦词先把朴素链路跑通把召回打印出来看一遍用一组小评测数据驱动调参你的效果大概率已经超过 80% 的临时方案。等到这 80% 都不够用了再根据评测数据决定要不要升级架构那时候你的每一步都会走得很踏实。现在这套 Spring AI RAG 已经在我的本地跑得比较稳了。我仍然保留一个习惯每次正式回答之前先看它会召回哪些文档片段。这个动作帮我省掉了大量调 Prompt 的时间。如果你也正在搭自己的知识库问答希望这次的记录能帮你少走一圈弯路。
返回列表