02 Naive RAG 存在的致命问题与解决方案 —— Building-RAG-Systems 项目系列第二篇(知识点较多)
关于本项目
项目名称:🚀 Building-RAG-Systems: From Theory to Production
本项目致力于提供一条平滑且深入的 RAG(检索增强生成)学习与实践路径。通过深度文章解析、配套 Demo代码 以及系统级项目实战,带你从零构建工业可用的大模型应用。 代码仓库链接:Github
📂 仓库结构 (Repository Structure)
本项目按照“理论 -> 配套demo -> 系统级实践”的逻辑组织,主要包含以下三个核心模块:
▼text复制代码📦 Building-RAG-Systems ┣ 📂 articles # 📖 核心知识库:RAG 理论解析、架构设计及痛点解决方案 ┣ 📂 articles_demo # 💻 Demo代码:与文章配套的开箱即用 Demo 代码 ┗ 📂 project # 🏗️ 系统级项目:融合所有知识点的全功能系统架构
1️⃣ articles:深入浅出的理论与思考
这里不只是空洞的理论,更是实打实的架构设计经验与问题解决思路。
- 探索 RAG 的核心组件与工作流。
- 深入剖析在 Chunking、Embedding、Retrieval 和 Generation 环节遇到的常见痛点(如语意鸿沟、上下文割裂、检索不精准等)及其解决方案。
2️⃣ articles_demo:配套的 Demo 代码
Show me the code! 每篇核心文章都配备了独立的、可运行的 Demo。代码保持极简,屏蔽无关框架干扰,方便你快速运行和理解。
3️⃣ project:从 Demo 到生产级系统
这是本仓库的终极目标。我们将前两个模块中沉淀的知识与技巧,融合并构建成一个完整的、具备 Agent 协同和复杂任务处理能力的系统级项目。
🗺️ 内容导航 (Roadmap & Navigation)
| 状态 | 文章 (Articles) | 核心要点 | 对应 Demo (Code Module) |
|---|---|---|---|
| 🟢 | 01 Native RAG 的标准处理流程 | 离线数据建库与在线相似度检索链路 | demo-01-naive-rag |
| 🟢 | 02 Naive RAG 存在的致命问题与解决方案 | 四大痛点:语意鸿沟、上下文割裂、多级跳转推理与全文总结能力缺失、缺乏纠错闭环 | demo-02-critical-issues |
| 🟡 | 03 工业级 RAG 项目还需要解决哪些问题 | 数据一致性同步、细粒度权限隔离、系统可观测性、自动化评估方案等多方面问题 | demo-03-other-issues |
| 🟡 | 04 RAG的元数据设计 | 元数据的三大应用场景,以及利用元数据解决资料冲突的方案设计 | demo-04-metadata-design |
| ⚪️ | 05 企业级 RAG 项目的架构参考 | 六层架构设计,从接入与网关层到最终的监控与测试层的全链路设计方案 | demo-05-system-architecture |
| ⚪️ | 06 RAG 的全链路耗时与优化 | 全面分析 RAG 的全链路耗时,并针对耗时过高的环节进行优化设计 | demo-06-latency-optimize |
| ⚪️ | 07 RAG 的评估维度以及标准评估方法 | 基于 RAGAS 的四大评估维度(精确度、召回率、忠实度、相关性),及结合 JUnit 的自动化裁判测试驱动开发 | demo-07-automated-evaluation |
| ⚪️ | 08 针对长短不一的文档的双轨制存储方案设计 | 结合全文检索与父子块检索,利用 Metadata 路由实现离线分轨入库与在线统一解析组装 | demo-08-dual-track-storage |
(注:🟢 已完成 🟡 进行中 ⚪️ 计划中)
- 关于文章: 文章初稿已全部推送到仓库,如果近期有 Agent 开发面试,希望提前看下八股的可以去仓库自取(顺手帮忙点个🌟就更好啦),当前为初稿,后续可能会随着code demo 做一些修改完善。后续每更新完一章的code demo会往导航同步更新一篇文章。
- 关于Demo Code: 所有文章配套的 demo code 会在Github仓库,均为本人本地跑通后的完整代码,clone后配置好相关后端组件后,可直接运行,如有需要请前往仓库自取。
- 关于生产级项目: 当前后端部分已写完近 4W 行代码,因为最近比较忙,同时多线程忙很多事,所以预计真正完成可能还需要1-2个月。完成后也会开源在Github仓库。项目参考了claude code 的一些实现,综合考虑了很多方面,包含:上下文窗口结构设计、长短期记忆管理机制(长短期转换、记忆编辑、遗忘、归档等)、多意图路由、复杂任务的执行清单拆分、Skills 渐进式纰漏手动实现、Mysql + Milvus + ElasticSearch + Redis 的多数据源协同链路等。
本篇正文
写在前面的话
本篇内容比较硬核,实现已经较为完善,推荐结合配套demo代码进行学习,代码只需配置好API Key、MySQL、ElasticSearch即可直接运行。本篇除了为了解决Navie RAG存在的:语意鸿沟、上下文割裂、多级跳转推理与全文总结能力缺失、缺乏纠错闭环四大痛点问题,而引入的:查询改写、假设性文档嵌入、意图路由、父子块索引、句子窗口检索、IRCoT、RAPTOR树状摘要、Self-RAG等多项高级 RAG 技术以外,还涵盖了专业的上下文窗口设计、长短期记忆机制、复杂意图的LLM逻辑逻辑+相似度匹配路由、skill渐进式纰漏的Java实现等核心技术实现。
项目介绍
本项目是一个基于 Java 17 + Spring Boot 3 + LangChain4j 构建的工业级检索增强生成(RAG)系统。旨在彻底解决传统朴素 RAG(Naive RAG)在真实业务场景中面临的词汇鸿沟、上下文割裂、多步推理失败、严重幻觉以及缺乏动态执行力等致命痛点。
系统融合了当前业界前沿的大模型落地实践,包括:Self-RAG(自我反思)、RAPTOR(树状宏观摘要)、IRCoT(交错检索思维链)、长短期记忆管理以及 Agentic Skill 渐进式披露。
朴素 RAG 虽然跑通了从本地数据到大模型问答的闭环,但在真实的工业级落地中,往往会遇到诸多致命痛点。简单来说,传统 RAG 就像一个死板的图书管理员:你问什么,他就死板地拿原始问题去匹配相似的段落,然后把那几段话硬塞给大模型。
在处理复杂的真实业务诉求时,这种机制暴露出四大核心缺陷。现行的工业界演进出了 Advanced RAG(高级 RAG) 和 Agentic RAG(基于智能体的 RAG) 来解决这些问题。
痛点 1:用户提问质量差与词汇鸿沟 (Query Mismatch)
致命问题:用户通常提问非常简短、口语化,甚至包含错别字(例如:“那个报错 HTTP 500 咋办”)。而知识库里的文档是非常正式的(例如:“内部服务器由于空指针异常导致系统崩溃”)。用口语化的短句去向量库里匹配专业长文,检索出的向量距离往往非常远,容易导致直接脱靶。
解决方案:查询转换
在去数据库检索之前,先让大模型对用户的原始问题进行一层加工。
Query Rewrite (查询重写):
让大模型把口语化的问题重写为更丰富、包含专业术语的检索词。
- 具体步骤:
- 把用户的原始提问(以及近几轮的历史对话)发送给 LLM。
- 利用系统提示词(Prompt)约束 LLM:“你是一个搜索词优化专家,请根据上下文,将用户的提问重写为更利于向量检索的独立句子,补充缺失的专有名词,纠正错别字。”
- LLM 输出重写后的查询,比如:“如何在 Nginx 配置文件中修改监听的端口号?”
- 拿着这句重写后的话,再去向量库做 Embedding 和检索。
- 工程考量: 这个步骤会增加一次大模型的调用,带来一定的延迟(Latency)。所以在 落地时,通常会选用响应极快的小模型(如 GPT-4o-mini 或 Qwen-Turbo)专门来干这个脏活累活。
HyDE (Hypothetical Document Embeddings,假设性文档嵌入):
面对用户提问,先让大模型“盲答”生成一段假设性答案(即使包含幻觉也没关系),然后把这段生成的答案向量化,去数据库里匹配真实的文档。因为“答案匹配答案”比“问题匹配答案”在向量空间中要精准得多。
HyDE(假设性文档嵌入)是学术界提出来的一个极其惊艳的思路。它的核心思路是:“问题”和“答案”在语义空间中长得并不像,但“假答案”和“真答案”长得却很像。
- 核心逻辑: 如果用户问:“Java 中 HashMap 的扩容机制是什么?” 如果直接向量化这个问题,它在多维空间里的特征是“疑问句”。但是向量库里存的都是陈述句(真实的文档)。 HyDE 的做法是:先让 LLM 直接回答这个问题(即不给任何参考资料,让它凭自己的参数记忆硬答)。LLM 可能会胡说八道(产生幻觉),但它生成的这段“假答案”,必然包含了大量的核心词汇、专业句式和语义模式(比如一定会提到“负载因子”、“数组”、“链表转红黑树”)。
- 具体步骤:
- 将原始问题发给 LLM,要求生成一段假设性回答(Hypothetical Answer)。
- (关键点) 将这段“假答案”进行向量化(Embedding)。
- 用假答案的向量,去向量数据库中寻找距离最近的 Top-K 真实文档。
- 最后,把找出来的真实文档喂给大模型,生成最终的正确回答。
- 为什么厉害: HyDE 巧妙地利用了大模型自身的“知识泛化能力”去充当检索的桥梁。即使假答案的数值或细节是错的,它的“语义骨架”却能完美匹配上知识库里真正的那篇技术文档。
Query Routing (意图路由):
判断用户的意图,例如:将“查询某人电话”路由到 SQL 数据库,将“查询公司报销制度”路由到向量数据库。
当你的系统不仅仅只有一份 PDF,而是拥有关系型数据库、图数据库、甚至是外部 API 时,硬把所有问题都塞进向量库是非常愚蠢的。查询路由让 RAG 拥有了“分发”的智慧。
- 核心逻辑: 这其实就是构建 AI Agent 最基础的工具调用(Tool Calling / Function Calling)机制。系统充当一个“路由器”,根据用户的意图,决定当前这个问题应该走哪条链路。
- 技术实现方案:
方案 A:大模型逻辑路由
给大模型提供一个工具列表的描述。例如:
▼text复制代码`Tool 1`:向量知识库(适合问文档内容、政策、技术原理) `Tool 2`:MySQL 数据库(适合问具体的订单状态、统计数据) `Tool 3`:Google Search API(适合问今天的新闻、实时天气)用户提问后,大模型首先输出一个 JSON 指令,告诉系统“这个问题我要调用 Tool 2”。Java 后端解析这个 JSON,去执行对应的 SQL 代码,然后再把结果返给大模型。
方案 B:语义路由
如果不想每次都等大模型推理,可以用向量做路由。预先设定几个“主题分类”的代表性句子并将其向量化。用户提问时,先计算问题向量和各个“主题向量”的相似度,距离谁最近,代码就走对应的分支逻辑。
补充讨论:
在真实的工业级落地中,这三种策略并不是单选题,而是被组合成一条高度协同的“流水线(Pipeline)”。
在成熟的架构中,这就如同后端系统里的网关分发、拦截器预处理和底层复杂查询。这三者在 RAG 系统中分别把守着不同的关卡。
第一步:总调度台 —— 意图路由 (Query Routing)
- 什么时候用:用户刚刚输入问题,系统接收到请求的第一瞬间。
- 扮演角色:流量网关 / 分发中心。
- 具体用法:
在真实的业务场景里,用户不会只问知识库里的内容。比如在一个企业级内部助手中:
- 如果用户问:“帮我查一下工号 89757 的考勤状态”(路由到 API/SQL 数据库,去查关系型数据)。
- 如果用户问:“你好,今天天气真不错”(路由到闲聊大模型,直接回复,不查任何库)。
- 如果用户问:“咱们公司最新的差旅报销标准是什么?”(命中,路由到 RAG 向量检索分支)。
- 工业级权衡:这是 AI Agent 最核心的能力。通过路由,系统可以极大地节省不必要的向量检索开销,并且避免大模型在闲聊时被强行塞入不相关的参考文档。
第二步:语境补全 —— 查询重写 (Query Rewrite)
- 什么时候用:当第一关的路由决定 “这个问题需要走 RAG 向量检索”,并且当前处于 “多轮对话” 的场景下。
- 扮演角色:上下文翻译官 / 预处理器。
- 具体用法:
一旦进入 RAG 分支,系统必须检查用户的提问是否缺胳膊少腿。
比如,用户上一轮问了“差旅报销标准”,系统回答后,用户紧接着问:“那出差打车的发票怎么贴?”这里的“那”和“出差”其实是依赖上一轮语境的。系统会调用一个极速的小模型(如 GPT-4o-mini),结合历史对话,把这句话重写为:“公司差旅报销标准中,打车发票应该如何粘贴报销?”
- 工业级权衡:几乎所有成熟的对话式 RAG 都会默认开启多轮对话重写。它的开销很小,但能让向量检索的命中率提升几个数量级。
第三步:高难度召回 —— HyDE (假设性文档嵌入)
- 什么时候用:作为向量检索的“兜底策略”,通常在标准检索(直接用重写后的问题去搜向量和 BM25)得分极低/找不到相关文档时触发,或者针对那些极其抽象、只有几个关键字的提问。
- 扮演角色:深度挖掘机。
- 具体用法:
假设用户问了一个极其简短但硬核的技术词:“ReentrantLock 底层原理”。
如果标准的向量检索找不到直接匹配的标题,系统就会触发 HyDE:先让大模型凭记忆写一段关于 ReentrantLock 的技术总结(假答案),然后拿着这段长篇大论去知识库里做向量匹配,把真正的技术文档“钓”出来。
- 工业级权衡:
在工业界,通常不会把 HyDE 作为默认开启的全局策略。 主要是由于:
- 太慢了:需要先等大模型生成一段假答案,再去做检索,最后再生成真答案。延迟翻倍,用户体验很差。
- 容易“带偏”:如果用户问的是企业极其私有、偏门的内部代号(大模型训练数据里根本没有),大模型生成的假答案会完全是胡编乱造。用这个完全南辕北辙的假答案去检索,不仅搜不到,还会把毫不相干的文档拉进来。
因此,HyDE 通常被配置为一条降级链路,或者只在特定需要深度发散的知识域(如长篇研报检索、专利检索)中选择性开启。
工业级全链路总结
如果把它们串联起来,一个成熟的处理流程是这样的:
- 用户提问 ➡️ 【Query Routing】 判断意图。
- 如果是闲聊或 API 查询,直接走旁路。
- 如果是知识库查询 ➡️ 【Query Rewrite】 结合历史记录,把问题改写成信息量饱满的专业长句。
- 拿着重写后的长句去进行混合检索(向量 + BM25)。
- 如果检索召回的置信度很高 ➡️ 直接交给大模型生成答案。
- 如果检索召回的置信度极低(没搜到)➡️ 触发 【HyDE】,让大模型生成假答案辅助深度召回。
- 最后大模型基于召回的最终优质文档,生成回答。
这种“搭积木”式的架构设计,正是目前很多优秀 Agent 框架(比如 LangGraph )在做的事情。
痛点 2:文本切分带来的上下文割裂 (The Chunking Dilemma)
致命问题:为了迎合 Embedding 模型的限制,文档被强行切成了小块(Chunk)。如果切得太小,检索很准,但大模型拿到的是碎片,不知道前因后果;如果切得太大,上下文有了,但检索精度急剧下降,库里全是噪音。
解决方案:从小到大检索 (Small-to-Big Retrieval)
将“用于检索的内容”和“喂给大模型的内容”解耦。
Parent-Child Chunking (父子块检索):
在切分时,把一个大段落(父块)切成几个小句子(子块)。在向量库中只对子块进行检索。一旦匹配到某个子块,系统会顺藤摸瓜找到它的父块,把整个完整的大段落发送给大模型。
核心思想:建两次索引,小块查,大块答。 我们将文档切分为较大的“父块”(例如一整个完整的章节或长段落),然后再将每个父块切分成更小的“子块”(例如一两句话)。只对“子块”进行向量化并存入向量数据库,而“父块”存入普通的 Key-Value 数据库(如 Redis、MongoDB 或内存 Map)。
当用户提问时,系统通过向量匹配命中极其精准的“子块”。然后,系统提取该子块元数据(Metadata)中的
parent_id,去 KV 数据库中把对应的完整“父块”取出来,将完整的父块发给大模型。技术实现步骤:
我们需要两个存储组件:一个向量数据库(存 Child)和一个文档型/KV 数据库(存 Parent)。
离线建库阶段:
第一步:切分为较大的父块 (比如按章节、双换行切分,假设每个父块 1000 字符),将父块存入 KV 数据库
第二步:将当前父块切分为更小的子块 (比如按句子切分),在子块的 Metadata 中强行注入 parent_id,将子块向量化并存入向量数据库在线检索阶段:
第一步:将用户问题向量化
第二步:在向量库中检索最相关的子块 (Child Chunk)
第三步:拿到子块后不直接用,而是去查它的父块,从 KV 库中捞出完整的父块上下文,为了防止重复添加相同的父块(如果有多个子块命中了同一个父块),可以加个去重逻辑
第四步:最终返回完整宏观的上下文给大模型适用场景:极其适合篇幅长、层级结构明确的文档(如 API 文档、法律合同、技术手册)。
Sentence Window Retrieval (句子窗口检索):
将文档按单句切分并向量化。检索时精准匹配到某一句,但在提取时,自动向前后各扩展 3-5 句话(滑动窗口),确保大模型看到的是一段连贯的上下文。
核心思想:精准定位一句,向上下文滑窗扩展。
这种方式不显式地划分父子级,而是将整篇文档按单句切分。每一个切分出来的句子都会带上一个严格递增的序列号(Index)。
在检索时,依然是精准命中得分最高的那一句话。但是在提取上下文时,系统会以这句话为中心,自动向它前、后各多取K句话(例如前 2 句 + 命中句 + 后 2 句),拼接成一个“窗口”发给大模型。
技术实现逻辑: 为了实现窗口滑动,向量数据库中不仅要存当前句,最好还能通过条件查询(基于 doc_id 和 index 区间)把相邻句子也取出来,比如 在 Metadata 中记录它是哪篇文档的第几个句子,或者在应用层内存中保留一份完整的顺序文档映射。
适用场景:适合没有明显段落层级、行文连贯的叙事类文本、小说、客服通话记录或是松散的维基百科文章。
痛点 3:无法处理“跨文档”和“全局总结”的复杂推理 (Multi-hop Reasoning)
致命问题:传统 RAG 只能做“单点查询”。如果用户问:“对比一下 A 项目和 B 项目在第三季度的核心技术突破”,这需要跨越多个不同文档去寻找线索。向量检索往往只能找到 A 的部分或者 B 的部分,无法像人类一样进行“多级跳跃(Multi-hop)”的逻辑关联;也无法回答“总结这 10 万字文档的三个核心主题”这种需要宏观视野的问题。
解决方案:知识图谱结合 (GraphRAG)
A Graph RAG Approach to Query-Focused Summarization (2024年微软提出)
这是目前工业界(尤其是微软大力推崇)解决复杂推理的经典方案。
- 技术实现:在离线建库阶段,不仅做向量切分,还利用大模型从文档中抽取出实体 (Entity) 和 关系 (Relationship),构建成知识图谱。
- 在线问答:当遇到复杂问题时,系统不仅做向量检索,还会去查询知识图谱,顺着图谱的“节点连线”把相关的概念全部串联起来,甚至利用图谱的社区检测算法(Community Detection)生成全局摘要。
GraphRAG 的核心思想是:化非结构化文本为结构化网络。 它不仅仅依赖文本块的字面相似度,而是让机器真正理解实体之间的“关系”。这种思路与我们在底层研发关系型数据库或图数据库时的索引构建非常相似。
1. 离线建库阶段
这部分是 GraphRAG 最烧钱、也最耗时的环节。
- 实体与关系抽取 (NER & RE):我们不再仅仅把文档切成 Chunk,而是把每一段文本喂给 LLM,让 LLM 提取出里面的节点(实体:人、地点、物品、概念)和边(关系:属于、制造、位于)。
- 构建图数据库:将提取出的节点和边存入专门的图数据库(如 Neo4j、NebulaGraph)。
- 社区检测与层级摘要 (微软 GraphRAG 的精髓):图谱建好后,算法会把连接紧密的节点聚类成一个个“社区(Community)”。比如,小说里“魔法学院”相关的所有老师、法术、建筑会被聚合成一个社区。然后,LLM 会预先为每一个社区生成一段全局摘要。这就解决了“请总结一下整本书里魔法体系的设定”这类宏观问题。
2. 在线检索阶段
当用户提出复杂问题时:
- 意图识别与实体提取:首先用 LLM 分析用户提问,提取出关键词(如“暗影长剑”、“冰霜巨龙”)。
- 图谱遍历 (Graph Traversal):使用图查询语言(如 Cypher)在 Neo4j 中以这两个实体为起点,进行深度优先或广度优先搜索,找出连接它们的“最短路径”或相关子图。
- 混合组装:将图谱中找到的逻辑关系(三元组或社区摘要),与传统的向量检索结果拼接在一起,作为无死角的 Context 发送给大模型。大模型据此就能顺畅地推理出答案。
其他替代解决方案:
除了 GraphRAG 这种“重工业”方案,业界还有几种非常流行且轻量级的解法,用来处理多级推理和全局总结。
1. Agentic RAG / 迭代检索 (Iterative Retrieval / IRCoT)
如果不建图谱,我们也可以通过赋予大模型“思考能力”来解决多级跳转。这种方案叫做 交错检索与思维链 (Interleaved Retrieval Chain-of-Thought)。
- 实现逻辑:大模型不再是一次性拿到所有文档,而是被设计成一个会“自我提问”的 Agent。
- 场景演示:用户问:“《指环王》导演的出生地是哪个国家的首都?”
- Agent 思考:我需要先知道导演是谁。➡️ 动作:检索“指环王 导演”。
- Agent 读取:检索回来的文档说导演是彼得·杰克逊。
- Agent 思考:现在我需要知道彼得·杰克逊的出生地。➡️ 动作:检索“彼得·杰克逊 出生地”。
- Agent 读取:文档说是新西兰的惠灵顿。
- Agent 思考:惠灵顿是新西兰的首都,问题解决。 ➡️ 输出最终答案。
- 优点:不需要维护复杂的图数据库,工程架构简单,只需编排 Agent 逻辑。
2. RAPTOR 树状摘要 (Recursive Abstractive Processing for Tree-Organized Retrieval)
RAPTOR: Recursive Abstractive Processing for Tree-Organized Retrieval (2024年斯坦福大学提出)
如果用户的痛点更多集中在 “全局总结”(比如:总结这份 100 页研报的核心观点),普通的向量检索只能捞出局部细节,而 GraphRAG 又嫌太重,那么 RAPTOR 是一套极其优雅的折中方案。
- 实现逻辑:它在离线阶段构建了一棵“摘要树”。
- 将底层文档切分成普通的 Chunk(叶子节点)。
- 用聚类算法(如 K-Means)把语义相近的 Chunk 聚在一起。
- 让 LLM 把这一聚类的 Chunk 浓缩成一段“高层摘要”(父节点)。
- 再把高层摘要继续聚类、继续总结,直到整本书变成了一段终极摘要(根节点)。
- 检索方式:用户提问时,系统从树根往下搜。如果是宏观问题(“整本书讲了什么?”),就会匹配到上层的宏观摘要;如果是细节问题,就会一路向下匹配到底层的 Chunk。它巧妙地融合了不同颗粒度的上下文。
方案选型建议
在实际的后端架构设计中,选择哪种方案取决于你的核心业务诉求:
- 侧重实体关系清晰、强逻辑推导(如金融风控股权穿透、公安刑侦、复杂小说设定):首选 GraphRAG (Neo4j)。
- 侧重文档的宏观阅读与层级总结(如财报分析、长篇研报问答):选择 RAPTOR 树状摘要。
- 开发资源有限,希望用敏捷方式解决动态推理:使用 Agentic RAG(迭代检索),用大模型的 API 调用次数换取工程架构的简单。
痛点 4:流程僵化,缺乏自我纠错能力 (Rigid Pipeline & Hallucination)
致命问题:传统 RAG 是单向的“流水线”:检索 → 生成。如果检索回来的内容全是错的,大模型依然会硬着头皮基于这些错误内容生成答案(引发新的幻觉)。它不知道什么时候该拒绝回答,什么时候该去搜索引擎查最新数据。
解决方案:Agentic RAG / Self-RAG (智能体与自我反思 RAG)
Self-RAG:自带“测试与纠错”的闭环系统
Self-RAG 彻底改变了单向数据流。它在流程中插入了多个“裁判(Grader)”节点,大模型不仅负责生成答案,还要负责自我评估。
核心机制拆解(三个核心裁判):
- 文档相关性评估 (Retrieval Grader)
- 动作:向量数据库返回 Top-K 个文档片段后,先不急着生成答案。大模型先对每一个片段进行打分(Yes/No)。
- 纠错逻辑:如果大模型认为找回来的文档和用户问题根本无关,它会拒绝使用这些文档,甚至触发“Query Rewrite(问题重写)”,换个搜索词重新去向量库查一遍。这相当于 Java 里的
while循环重试。
- 幻觉评估 (Hallucination Grader)
- 动作:大模型基于相关的文档生成了草稿答案。此时,再调动大模型审视自己的草稿:这个答案里提到的每一个数据、每一个事实,都能在刚才的参考文档里找到出处吗?
- 纠错逻辑:如果发现自己“加戏”了(比如文档里没写具体时间,但草稿里捏造了 2025 年),它会打回重做,重新生成不包含幻觉的答案。
- 答案有用性评估 (Answer Grader)
- 动作:检查最终答案是否真正回答了用户的原始提问。如果只是东拼西凑了一堆相关事实,但没有直击痛点,同样打回重写。
具体落地框架:目前最主流的实现方式是使用 LangGraph(或类似的图流编排工具)。可以把整个 RAG 定义为一个包含多个节点的有向循环图,通过条件边(Conditional Edges)来决定是输出答案,还是回退到检索节点。
存在的问题及解决:
在引入循环反馈机制后,如果设计不当,系统会像遇到了死锁一样,在一个“检索 -> 评估不相关 -> 重写问题 -> 再次检索 -> 依然不相关”的死循环里无限消耗大模型的 Token 和时间。
在实际的后端和 Agent 开发中,我们解决这个问题的核心思想非常成熟:状态机(State Machine)结合熔断机制(Circuit Breaker)。
在像 LangGraph(或者 Java 生态中类似的图流编排框架)的实现里,节点之间传递的不再是简单的字符串,而是一个全局的“状态对象(State)”。我们通过在这个状态对象中维护计数器和流转记录,来精准控制图的走向。
核心设计:状态对象 (The State)
在进入流程之前,我们需要定义一个伴随整个请求生命周期的上下文对象。
实际工作链路 (4 步流转)
第一步:检索节点 (Retrieve Node)
系统接收到
RagState,提取currentQuery去向量数据库中执行检索。检索到的结果存入state.documents,然后将控制权交给下一个节点。第二步:评估节点 (Grade Node)
大模型作为“裁判”,检查
state.documents是否包含回答currentQuery所需的信息。
- 如果相关:在状态中打上
grade = "PASS"的标记。- 如果不相关:在状态中打上
grade = "FAIL"的标记。第三步:条件路由节点 (Conditional Router) —— 熔断的核心
这是一个纯逻辑判断节点(不需要调用大模型)。它检查当前的
grade和retryCount,决定下一步去哪儿:
- 分支 A (成功通行):如果
grade == "PASS",直接流转到 “生成答案节点 (Generate Node)”。- 分支 B (触发熔断,优雅退出):如果
grade == "FAIL"并且state.retryCount >= MAX_RETRIES,此时系统强制跳出循环,进入 “降级/兜底节点 (Fallback Node)”。- 分支 C (允许重试):如果
grade == "FAIL"并且state.retryCount < MAX_RETRIES。 流转到 “重写问题节点 (Rewrite Node)”,同时state.retryCount++。第四步:兜底策略 (Fallback Node 的具体实现)
当系统确定“我已经在本地库里努力找了 3 遍,并且换了 3 种搜索词,依然找不到答案”时,必须给用户一个交代。常见的优雅退出策略有三种:
- 策略 1:诚实拒绝 (Graceful Decline):系统不调用大模型生成答案,而是直接返回一段预设的、友好的提示词:“抱歉,在当前的知识库中未找到与您问题高度相关的信息。为了避免提供错误答案,我无法直接回答。您可以尝试提供更多背景信息,或联系人工客服。”(这彻底杜绝了幻觉)。
- 策略 2:转移阵地 (Web Search Fallback):既然本地没有,那就去公网找。系统会调用外部搜索引擎 API(如 Google Search API、Tavily API),用最后一次的搜索词去互联网上抓取最新的网页内容,然后基于网页内容生成答案。
- 策略 3:人工介入 (Human Handoff):在企业内部系统中,记录下这条失败的 Query,并将其打上“知识盲区”的标签,发送到运维或业务人员的钉钉/企业微信群,提示后台需要补充相关的知识文档。
这里有一个重要的工程细节:如果只做
retryCount++,而不改变搜索词,那去数据库里查 100 遍,查出来的依然是相同的垃圾数据(因为检索是幂等的)。所以,在 “重写问题节点 (Rewrite Node)”,我们必须采取 降级重写策略 (Degrading Rewrite):
- 第 1 次重试:让大模型尝试换同义词(比如把“打车发票”换成“交通费凭证”)。
- 第 2 次重试:让大模型尝试抽象化问题,去掉一些限定词(比如去掉具体的日期限制,只搜核心概念)。
- 第 3 次重试:触发 HyDE(假设性文档嵌入),用大模型的假答案去“钓鱼”。
通过这种方式,每一次回退的检索都是一次“全新的尝试”,直到触碰
MAX_RETRIES的天花板。在架构上,防死循环的本质就是:状态追踪 + 阈值拦截 + 兜底旁路。这种设计模式让 RAG 系统具备了极强的韧性和确定性,不会因为大模型一时的“执念”而拖垮整个后端的资源。
Agentic RAG:向量检索只是“工具箱”里的一件工具
在真实的业务中,问题往往极其复杂,单靠查文档是解决不了的。Agentic RAG 将大模型升级为“大脑(Router & Planner)”,而向量数据库(RAG)退化成了与 MySQL、API、代码执行器平级的 “工具(Tool)”。
具体业务案例分析:广告推送策略的综合诊断
假设你的平台包含两部分数据:
- 关系型数据库 (MySQL/ClickHouse):存储硬性的业务指标(如各个广告推送任务的每日点击率 CTR、转化率、用户圈选维度)。
- 向量数据库 (Milvus):存储非结构化的业务文档(如每个季度“大促活动”的运营策略文档、文案审核规范、历史复盘报告)。
用户提问:
“帮我对比一下上个月‘春季数码大促’的实际推送点击率和我们的历史基准线,并总结一下这次大促核心使用了什么差异化的推送策略。”
传统 RAG 的死局: 传统 RAG 会把这句话拿去向量库搜,搜不出点击率数据(因为在 MySQL 里);或者去查 SQL,查不出策略总结(因为在文档里)。系统直接崩溃或胡说八道。
Agentic RAG 的解决路径(基于 ReAct 模式:Reasoning + Acting):
大模型接收到这个问题后,开始在后台进行“思考”与“工具调用”的循环:
- Iteration 1(查数据)
- Thought(思考):我需要先获取“春季数码大促”的实际点击率和历史基准线。这属于结构化指标,我应该调用数据库查询工具。
- Action(动作):调用
SQL_Query_Tool,传入参数campaign_name="春季数码大促"。 - Observation(观察/结果):后端执行 SQL 返回
{"实际CTR": "4.2%", "历史基准CTR": "3.5%"}。
- Iteration 2(查文档)
- Thought(思考):指标拿到了,表现不错。现在我需要总结这次大促的核心差异化策略。这属于非结构化文档,我应该调用向量知识库检索工具。
- Action(动作):调用
Vector_DB_RAG_Tool,传入搜索词“春季数码大促 核心推送策略 差异化”。 - Observation(观察/结果):向量库返回相关文档片段
“本次春促一改往日的全量广撒网,主要采用基于用户历史浏览偏好的分时段、动态文案推送,针对流失预警用户增加了额外补贴...”。
- Iteration 3(综合与输出)
- Thought(思考):我已经收集齐了所有需要的信息。现在可以回答用户了。
- Final Answer(最终输出):“上个月的春季数码大促表现优异,实际点击率为 4.2%,高于 3.5% 的历史基准线。根据策略文档复盘,本次大促成功的原因在于其差异化的推送策略:放弃了全量广撒网,改为基于浏览偏好的动态文案推送,并针对流失预警用户精准投放了补贴。”
技术实现方案 (基于 Java & LangChain4j)
在 Java 生态中实现 Agentic RAG 非常优雅。LangChain4j 提供了 @Tool 注解,可以极快地把现有后端的 Service 方法变成大模型的工具。
然后,在初始化大模型时,把这些工具塞进去,大模型就会自动根据用户的提问,决定调用哪个 Java 方法、传什么参数,这就形成了一个强大的 AI Agent 闭环。
通过将 RAG 降维成 Agent 的一个工具,并结合 Self-RAG 的评估机制,大模型的输出变得极其可控和精准。
配套代码解析
📂 核心目录结构
▼text复制代码src/main/java/com/example/demo02criticalissues/ ├── chat/ # 大模型配置 (DashScope Qwen) ├── chunker/ # 高级切分策略 (父子块切分 ParentChildChunker、句子滑窗 SentenceWindowChunker) ├── context/ # 上下文窗口管理 (ContextWindow 结构化提示词设计) ├── controller/ # REST API 对外接口 ├── entity/ # 数据库实体类 (对话归档、长期记忆(用户画像)、RAPTOR 树节点等) ├── repository/ # Spring Data JPA 仓储接口 ├── retriever/ # 检索器与重排序 (HybridRerank 混合重排、IRCoT 迭代检索器) ├── router/ # 意图网关 (LLM 逻辑路由、Semantic 语义路由) ├── service/ # 核心业务逻辑层 │ ├── AdvancedRAGService.java # RAG 核心主控流水线 (主调度器) │ ├── HyDEService.java # 假设性文档生成增强检索 │ ├── QueryRewriteService.java # 查询重写 (术语规范化、上下文补全) │ ├── RAPTORService.java # RAPTOR 树状摘要构建与检索 │ ├── SelfRAGService.java # 自我反思与评判引擎 (防止幻觉) │ ├── ShortTermMemoryService.java # 短期记忆滚动压缩管理 │ └── UserProfileService.java # 长期记忆 (用户画像) 提取与更新 ├── skill/ # Agentic Skill 管理引擎 (渐进式披露加载) ├── tools/ # 智能体工具 (Text2SQLTool 数据库与系统脚本执行) ├── utils/ # 工具类 (JSON 安全提取等) └── test/ # 核心功能演示入口 (AdvancedRAGTest)
🧠 核心技术实现方案
1. 结构化上下文窗口设计 (Context Window)
抛弃了传统 RAG 简单的“资料+问题”拼接,设计了类似 XML 标签的结构化 Prompt 容器。
-
机制: 划分为
<system prompt>,<core rules>,<tools or Skills>,<memory>,<retrieved context>,<input>六大模块。 -
优势: 严格界定大模型的行为边界,防止提示词注入;让模型时刻保持一致的人设和记忆连贯性。
2. 双轨记忆管理 (Long/Short Term Memory)
-
短期记忆 (Rolling Summary): 防止对话轮数过多导致 Token 爆仓。当历史对话达到阈值(如3轮)时,自动触发大模型将新对话“融合”到旧的全局摘要中,生成新的滚动摘要,随后清空历史,实现无损降维。
-
长期记忆 (User Profile): 通过异步分析对话摘要中的兴趣点与高频词,更新存储在数据库中的用户画像(如专业水平、提问偏好),后续对话自动注入以实现高度个性化。
3. 多维查询重写 (Query Rewrite)
填平用户口语化提问与专业文档之间的“词汇鸿沟”。
-
术语规范化: 将“那个发票咋贴”翻译为“差旅交通费凭证粘贴规范”。
-
上下文补全: 利用短期记忆,将包含代词的追问(如“那打车的呢?”)补全为携带独立完整语义的检索长句,大幅提升向量检索命中率。
4. 假设性文档嵌入增强 (HyDE)
-
机制: 遇到缺乏上下文线索的极其抽象的技术提问(如“HashMap扩容原理”)时,先让 LLM 凭训练记忆“盲答”生成一段假答案。用这段富含专业特征词汇的假答案去向量库中做匹配。
-
优势: 通过“答案匹配答案”的降维打击,将原本极低的召回置信度拉升至精准召回。
5. 双层意图路由网关 (Intent Routing)
-
语义路由 (Semantic Router): 基于本地轻量级向量计算,将用户 Query 与预设的“闲聊/文档查询/SQL查询”主题向量计算余弦相似度,0延迟、无 Token 消耗,作为第一道高频流量分发网关。
-
逻辑路由 (LLM Router): 基于大模型强大的推理能力,处理复杂的隐含意图,作为兜底防线。
6. 破除上下文割裂的切分策略 (Chunking Strategies)
-
父子块检索 (Parent-Child): 建两次索引,切分小句入向量库,大段落入关系库。小块精准命中后,顺藤摸瓜返回完整大段落,给大模型提供充分的前因后果。
-
句子窗口检索 (Sentence Window): 按单句切分,命中某句后,自动向前后滑动扩展 K 句话,拼装成连贯上下文。
7. RAPTOR 树状摘要与宏观问题检索
解决传统 RAG 无法回答“请总结这100页研报核心观点”的痛点。
-
构建: 底层 Chunk -> K-Means 聚类 -> LLM 生成局部摘要 -> 再次聚类... 最终生成 Root 顶层全局摘要。
-
检索: 基于用户问题意图动态决定探查树的层级。宏观问题直接抓取 Root 摘要,微观问题探底至叶子节点。
8. IRCoT 交错检索思维链
处理跨越多个文档才能拼凑出答案的多跳(Multi-hop)推理。
- 系统转变为一个会“自我提问”的 Agent。执行
思考 -> 搜索短句 -> 观察 -> 再思考生成新搜索词的循环,直到收集齐所有拼图。
9. Self-RAG 自我反思与 Fail-Fast 熔断机制
抛弃传统的“盲目检索+强行生成”单向流水线,引入大模型裁判委员会。
-
三大裁判: 检索相关性裁判、幻觉裁判、答案有用性裁判。
-
Fail-Fast (快速熔断): 在裁判判定失败时(尤其是严重脱离文档的幻觉),系统绝不死循环浪费 Token,而是快速阻断并回退到安全的兜底策略(如婉拒回答或请求人工介入)。
10. Agentic Skill 渐进式披露与系统防线
-
渐进式披露: 系统中预置了复杂的工具说明(如 skill.md)。平时这些内容不载入内存;只有当路由判定需要执行复杂任务(如 Text2SQL)时,才动态加载包含详细表结构、参考规范 (refs) 和脚本列表 (scripts) 的长 Prompt,极大节省常规对话的 Token 消耗。
-
防 OOM 与注入拦截: Text2SQL 工具内置了指令级注入拦截,并在下发底层执行前强制追加 LIMIT 防护,彻底杜绝大模型生成的恶劣 SQL 导致千万级数据撑爆 JVM 的严重线上事故。
🔌 REST API 文档
基础路径: /api/rag
1. 对话接口 (POST /chat)
请求体:
▼json复制代码{ "userId": "user_001", "sessionId": "session_1001", "query": "咱们公司最新的差旅发票应该怎么贴?" }
响应体:
▼json复制代码{ "answer": "根据差旅报销规范,交通费发票必须平铺粘贴在A4纸左上角...", "sessionId": "session_1001", "reasoning": "匹配到主题: 向量库检索", "retrievalStrategy": "VECTOR_SEARCH", "usedTools": ["IntentRouting", "VectorSearch", "QueryRewrite", "HybridRerank", "SelfRAG"] }
2. 意图分析独立测试 (POST /intent)
请求体:
▼json复制代码{ "userId": "user_001", "query": "帮我统计一下数据库里的总用户数" }
3. 系统健康检查 (GET /health)
响应体:
▼json复制代码{ "status": "OK", "message": "RAG服务正常运行" }

