这个系列写到《走进 AI Agent》的第四篇,我打算把镜头对准一个容易被低估的模块:知识获取管道。前面聊过了 Agent 的基础结构、规划能力和工具调用,但一个只能思考、没有知识来源的 Agent,就像刚毕业的高材生,推理能力再强,面对具体业务问题时手里没有资料,照样抓瞎。RAG(Retrieval-Augmented Generation)就是目前最接地气、最适合团队从 0 到 1 落地的知识获取方案,同时也是 RAG 项目、本地知识库和 AI Agent 开发绕不开的核心话题。
这篇我打算按实战路线来写:先说明为什么 Agent 需要 RAG,再把管道一层层拆开讲清楚,最后给一套基于 LangChain 的本地 RAG 代码示例,以及对检索质量、生产落地、Agentic RAG 的经验思考。如果你正在搭 AI Agent,或者做知识库相关的东西,这篇应该能帮你少踩不少坑。
1. 为什么 AI Agent 需要一条知识获取管道
1.1 大模型的天生短板:知识截止与幻觉
先说一个几乎每个 LLM 应用团队都会撞上的事实:大模型的知识是“有截止日期”的。模型在被训练完的那一刻,掌握的信息就基本固定了。它不知道你公司的报销制度,不知道昨天发布的版本功能,也不知道某个具体客户反馈了什么问题。你可以在提示词里塞一段背景,可一旦内容超过上下文窗口,或者每次都靠人工复制粘贴,这事就没法规模化。
比知识截止更麻烦的是幻觉。模型在不知道答案时,并不会诚实地告诉你“我不知道”,而会更倾向于生成一段听起来很合理的回答。放在对话场景里可能只是尴尬,放在 Agent 自动执行任务、自动回复客户的场景里,就是事故。我见过不少团队一开始只拿模型+提示词做客服问答,结果线上被用户截图投诉,原因就是模型把不存在的功能说得像真的一样。
所以要让 Agent 真正“知道”某个领域的知识,不能指望把知识写进模型权重里,而是要给它修一条获取知识的管道。知识获取管道让模型在回答之前先去查资料,用查到的资料来约束生成。这本质上是从“闭卷考试”变成“开卷考试”,模型仍然是那个会答题的人,但手里的参考资料是真实、可更新、可追溯的。
1.2 RAG 在 Agent 架构中的位置
一个完整的 AI Agent 通常包含规划、记忆、工具调用和行动执行四块。RAG 可以算作“记忆”的一部分,但又不完全是记忆。因为 Agent 的长期记忆通常需要支持写入和检索,而 RAG 对外提供一个标准接口:你给我一个问题,我从知识库里找出相关的文本片段还给你。
在实际系统里,RAG 往往有两种接入方式。一种是直接参与生成:用户提问后,系统先从知识库检索上下文,再把上下文和问题一起交给模型生成回答。另一种是把 RAG 包装成 Agent 的一个工具:规划器决定“这个问题需要查知识库”,于是调用检索器,拿到结果后再决定下一步动作——是直接回答,还是再查一次,还是换一个知识库,甚至调用其他工具补充信息。
我经常被问到“Skill 和 RAG 怎么结合”。这两者不冲突,Skill 解决的是“怎么做”,RAG 解决的是“依据是什么”。比如客服 Agent 的 Skill 定义的是退款流程怎么走、需要几个确认步骤;而 RAG 负责提供这个产品的具体退换货规则。Skill 告诉 Agent 动作顺序,RAG 告诉它每一步背后的业务依据。把它们拆开设计,之后的维护成本会低很多。
1.3 为什么不是微调或超长上下文
每次聊到知识获取,总有人问:“为什么不直接微调模型?”我的回答是:能接受成本你可以试,但微调适合改变模型的风格、输出格式、专业术语习惯,不适合做事实型知识更新。微调的成本包括数据标注、GPU 训练、模型版本管理,而且一旦知识变了又要重新训练。更现实的问题是,微调后模型依然会产生幻觉,你没法掰开它的嘴确认某句话到底来自哪份资料。
还有人会问:“现在上下文窗口不是越来越大吗?把所有文档都塞进提示词不就行了?”把文档全塞进去,短时间内好像解决了“知道”的问题,但成本会暴涨,而且长上下文里有大量无关信息,注意力会被稀释,模型反而更容易抓不住重点。检索的目的不是简单地把资料放到模型面前,而是把“最相关的几段”放到模型面前。这跟开会前先看会议纪要,而不是把公司三年邮件全打印出来是一个道理。
RAG 的核心价值就在这里:知识是外置的,更新知识库不用重新训练模型;知识是可控的,每条回答都能追溯到来源文档;知识是弹性的,可以一个 Agent 挂多个知识库,也可以按业务动态调整。
2. 拆解 RAG 管道:从文档到答案的完整链路
2.1 RAG 的本质:先查资料,再写报告
RAG 的官方解释是“检索增强生成”,翻译成人话就是:模型不直接凭记忆回答,而是先在一堆资料里找出相关内容,再结合这些内容组织答案。放进现实场景里,它很像一个研究员的工作方式:接到课题后先去档案室查材料,把关键段落摘出来,最后写报告,而且报告里必须标注引用来源。
这听起来不难,但要把它做成一个稳定可用的知识获取管道,比想象中复杂得多。数据是脏的,格式是乱的,用户问法千奇百怪,检索出来的内容可能答非所问。所以 RAG 不是“向量数据库+大模型”两行代码就完事,而是由很多细节堆出来的工程系统。
2.2 六段式管道概览
我习惯把 RAG 管道拆成六个阶段:接入、分块、向量化、存储、检索、生成。每个阶段都有独立的输入输出和易错点,把它们分开看,后面排查问题会清晰很多。
| 阶段 | 主要任务 | 常见技术选型 | 关键产出 |
|---|---|---|---|
| 文档接入 | 加载 PDF、Word、HTML、Markdown 等原始文件 | PyPDF、Unstructured、Tika | 清洗后的纯文本 |
| 分块 | 把长文本切成适合检索的片段 | RecursiveCharacterTextSplitter、语义切分 | 结构化的 Chunk 列表 |
| 向量化 | 把文本转成向量,让语义相近的内容在空间中靠近 | bge、text-embedding-3、m3e | 文本对应的 Embedding 向量 |
| 存储索引 | 存入向量库并建立索引 | Chroma、FAISS、Milvus、pgvector、Qdrant | 可检索的向量索引 |
| 检索排序 | 根据用户问题找回相关内容,并做重排 | 向量相似度、BM25、混合检索、Reranker | Top-K 相关文本片段 |
| 生成引用 | 把检索结果与用户问题组装成 Prompt,交给模型生成 | Prompt 模板、大模型、引用溯源 | 带依据的最终回答 |
这个管道最容易被忽略的是“接入”和“生成”两头。很多人一上来就做向量化,结果发现 PDF 里的表格变成了乱码,或者检索到了内容但模型没按内容回答。骨架搭好之后,大概率要在两端来回调。
2.3 索引阶段的设计要点
索引阶段直接决定了知识库的“底子”。如果文档加载和分块做得糙,后面无论怎么调检索都补不回来。我在这块踩过的坑比后面任何环节都多。
文档接入不能只调一个库就完事。PDF 要区分文字版和扫描版,扫描版得先 OCR;PPT、表格这类结构化文件要尽量保留层级信息。我处理产品手册时,经常发现关键参数在表格里,表格一旦被简单按行拆开,语义就全断了。所以接入阶段不要怕麻烦,宁可多花时间把文档结构搞清楚,也不要直接一把梭。
分块策略更是重灾区。常见做法是用 RecursiveCharacterTextSplitter 按固定字符数切,但我强烈建议先按文档结构切,比如 Markdown 标题、PDF 章节、HTML 标题,再结合段落切。单块大小一般控制在 200 到 800 个 token 之间,同时保留 10% 到 20% 的重叠。块太大,向量里噪声多,检索精度下降;块太小,语义不完整,模型拿到手也读不出完整逻辑。不要迷信网上某个“最佳实践”,不同业务语料的最优块大小不一样,后面我会专门讲怎么用 hit rate 验证。
Embedding 模型选型也要看场景。中文场景里开源模型像 bge-m3 这类效果已经不错,英文场景选择更多。Embedding 模型一旦选定了,入库和查询必须用同一个模型,换模型就意味着重新索引。向量库的选择则看数据量和团队运维能力:小项目本地用 Chroma 或 FAISS 很轻量;上了规模、要支持权限过滤和高并发,再考虑 Milvus 或 pgvector。
3. 实操:基于 LangChain 从零搭一个本地 RAG
3.1 环境准备与依赖安装
这一节我按最常见的本地 RAG 项目来写:用 LangChain 做管道编排,本地加载一个 PDF 文档,切片后向量化,最后提问和回答。先装依赖,我建议用干净的 Python 3.10 以上环境:
pip install langchain langchain-community langchain-huggingface chromadb pypdf如果你的网络环境里 HuggingFace 下载模型不方便,可以把 Embedding 模型换成本地已有的模型目录,或者直接用浏览器下载模型文件后加载本地路径。我自己的习惯是先把原始文本抽取出来单独跑一遍,确认能正常加载后再连向量库,这样出了问题更容易定位。
3.2 文档加载与分块实现
下面这段代码加载一个本地的 PDF 文件,并按字符数做初步切块:
from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter loader = PyPDFLoader("./product_manual.pdf") documents = loader.load() text_splitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50, separators=["\n\n", "\n", "。", ";", ",", " ", ""] ) chunks = text_splitter.split_documents(documents) print(f"切分完成,共 {len(chunks)} 个文本块")注意加载回来的 document 对象通常自带page_content和metadata,metadata 里一般有页码信息,这个后面做引用溯源很有用。有些 PDF 页眉页脚会混进来,最好在加载后用正则先清理一遍。切分器里的separators顺序是有讲究的,它是按优先级逐个尝试的,优先按段落切,实在不行再按标点切,最后才按字符硬切。这样做能最大限度保留语义完整性。
3.3 向量化与入库
接下来选择 Embedding 模型并把切片写入向量库。这里用 HuggingFace 的 bge-m3 做示例,你也可以换成其他模型:
from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma embedding_model = HuggingFaceEmbeddings( model_name="BAAI/bge-m3", model_kwargs={"device": "cpu"}, encode_kwargs={"normalize_embeddings": True} ) vector_store = Chroma.from_documents( documents=chunks, embedding=embedding_model, persist_directory="./chroma_db" )normalize_embeddings建议打开,这样后续计算相似度时会稳定一些。Chroma 的persist_directory是数据落盘的位置,测试阶段直接删掉重新跑也不心疼。如果你后续要换 Embedding 模型,记得连这个目录一起删掉重新索引,否则新旧向量混在一起会影响检索结果。
3.4 检索与生成串联
向量库建好后,可以把它变成检索器,再组装回答链路。这里用最基本的 LCEL 写法:
retriever = vector_store.as_retriever( search_type="similarity", search_kwargs={"k": 4} ) from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser from langchain_openai import ChatOpenAI llm = ChatOpenAI(model="gpt-4o-mini", temperature=0) prompt = ChatPromptTemplate.from_messages([ ("system", "你是一个企业知识库助手。请仅根据下面的资料回答问题,不要凭记忆编造。" "如果资料不足以回答,请直接说“根据现有资料无法回答”。\n\n资料:{context}"), ("human", "问题:{question}") ]) def format_docs(docs): return "\n\n".join(doc.page_content for doc in docs) rag_chain = ( {"context": retriever | format_docs, "question": lambda x: x["question"]} | prompt | llm | StrOutputParser() ) result = rag_chain.invoke({"question": "这个产品支持哪些连接方式?"}) print(result)这段代码的核心就是把检索出来的内容先格式化,然后塞进 Prompt。把temperature调成 0 是刻意为之,RAG 场景下尽量让模型克制一点,少一点创造性,多一点忠实度。系统提示词里那句“资料不足就直说”非常重要,这是抑制幻觉的第一道防线。
3.5 不同技术栈的落地参考
LangChain 只是其中一种选择。Python 生态之外,Java 项目可以关注 LangChain4j 和 Spring AI,两者都对 RAG 流程做了不错的封装,尤其是 Spring AI 提供了与 Spring Boot 紧密结合的接口,适合企业级应用平台把 RAG 能力嵌进现有服务。C#/ .NET 团队则可以看 Semantic Kernel,它把记忆、规划、工具调用统一在了一套抽象里。我见过不少 ERP 加 RAG 加 LLM 的产品检索需求,团队技术栈五花八门,其实核心流程都差不多:文档入库、切片、向量化、检索、生成,差别只在框架 API 上。
4. 检索质量:决定 RAG 天花板的关键
4.1 先用 Hit Rate 给检索效果定标
RAG 生成质量的上限是检索质量。如果检索出来的资料本身不含正确答案,模型再强也答不对。所以我做 RAG 项目第一件事,不是调 Prompt,而是先做检索评测。
最常用的一个指标叫 Hit Rate,也叫 Recall@K,意思是:在一个评测问题集里,有多少比例的问题,正确答案出现在检索结果前 K 个片段中。比如你有 100 条测试问题,设置 K=4,如果其中 80 条问题的正确答案出现在 Top-4 里,Hit@4 就是 80%。这个指标非常直观,用来验证分块策略、Embedding 模型和检索方式是否有效。
除了 Hit Rate,我还会看 MRR(Mean Reciprocal Rank)。它衡量正确答案在结果里排得多靠前,如果正确答案排第一,这一条得 1 分,排第二得 0.5 分,依此类推。对于“用户只看第一屏结果”的场景,MRR 比 Hit Rate 更贴近体感。生成质量方面,可以用 RAGAS 这类框架评估 faithfulness 和 answer relevance,但前提是检索已经过关。
4.2 分块大小与 Embedding 模型怎么调
我试过一个典型的中文产品知识库:512 字符分块,Hit@4 约 82%;切成 256 字符后,Hit@4 掉到 74%;改成 1024 字符后,Hit@4 是 79%。看起来 512 最好,但换一个文档结构完全不同的语料,结论可能反过来。分块大小的选择要跟着语料走:如果一段话里包含完整的问答对,用小块更好;如果业务描述逻辑连续、跨越多个段落,就需要大块甚至父子分块。
下面是不同分块大小的表现特征,可以对照自己的场景来选:
| 分块大小 | 优点 | 风险 | 适合场景 |
|---|---|---|---|
| 128-256 | 检索精准,噪声少 | 语义可能被切断 | 问答对、规格参数说明 |
| 400-600 | 效果均衡,通用性强 | 需要重叠避免切碎 | 大部分产品手册、制度文档 |
| 800-1200 | 上下文完整度高 | 噪声增加,成本上升 | 技术方案、长段落报告 |
| 父子分块 | 既有精度的又有上下文 | 实现复杂些 | 对回答质量要求高的业务 |
Embedding 模型对比不能只看常识问答。我建议拿自己的业务语料,跑几条典型问题,看相似度排序是不是跟直觉一致。有些模型在通用语义上很强,但遇到产品型号、代码片段、行业黑话就表现一般。必要时可以做一个小规模测试集批量对比 Hit@K,用数据说话。
4.3 混合检索与重排:从“能用”到“好用”
纯向量检索的问题在于它只懂“语义”,不太擅长处理精确匹配。比如用户查“A100 型号”,向量检索可能把“A1000”也检索出来;反过来,用户输入有错别字或专有名词缩写,向量检索也可能找不到。解决办法是做混合检索:把传统关键词检索(BM25)和向量检索结果合并,再重新排序。
重排是另一个性价比很高的优化。第一次用向量检索先把候选集扩大到 20 到 50 条,再用一个更强的 reranker 模型做精排,只保留 Top-4 或 Top-5 给大模型。这种做法能明显提升准确率。常见方案有 bge-reranker 系列,部署也不复杂。重排阶段虽然多了几步计算,但换来的是回答质量的稳定提升,生产环境我基本都会加。
4.4 查询改写与 Metadata 过滤
用户问的问题往往很短,比如“这个怎么退?”,如果知识库里都是完整句子,直接拿这句话去检索效果不会太好。可以先让大模型把问题补全,比如改写成“这个产品如何申请退货退款?”,再去做检索。技术上可以叫查询改写或 Query Rewriting。还有一种思路叫 HyDE,就是先让模型根据问题生成一段伪答案,再用伪答案去做相似度检索,等于把“模糊的问题”变成“接近答案的文本”,在某些场景下效果有惊喜。
Metadata 过滤容易被忽略,但它能大幅减少无关结果。比如知识库里同时包含多个产品线的文档,每条 chunk 都打上“产品线”“文档类型”“日期”等标签。检索时先根据用户问题过滤产品线,再做相似度检索。这比把所有文档混在一起检索要准得多,尤其是知识库变大了以后,Metadata 过滤是必选项。
5. 进阶:从 RAG 到 Agentic RAG
5.1 普通 RAG 不够用的时候
基础 RAG 流程是“检索一次、生成一次”,它假设问题和资料是一一对应的。但现实中的问题往往更复杂:用户先问“这个设备支持远程控制吗?”,再追问“那报警功能怎么设置?”,或者问“对比一下 A 型号和 B 型号的功耗差异”。这类问题需要多次检索、多文档对比,甚至要根据前面的回答来调整下一步检索。普通 RAG 管道比较傻,一次检索不到正确信息,它不会换个姿势再查一次。
这时候 RAG 就需要具备“智能体”的特征:让 Agent 来决定查什么、查几次、查完之后是否继续行动。这个概念现在常被称为 Agentic RAG,也就是把 RAG 能力封装成 Agent 的工具或行动策略,用规划能力去调度检索过程。
5.2 Agentic RAG 的核心模块
我理解 Agentic RAG 不单单是“给 RAG 加个循环”,而是要让系统具备四个能力。第一个是查询理解,能识别出复杂问题需要拆解成子问题;第二个是检索策略选择,能决定用向量检索、关键词检索、SQL 查数据,还是调用外部搜索;第三个是记忆,能记住之前的检索结果和对话上下文,避免重复问同样的问题;第四个是自我校验,能在生成回答前检查检索结果是否足够支撑结论,不够就回头再查。
打个比方:普通 RAG 是“图书管理员”,你问什么他拿什么;Agentic RAG 是“专家助理”,他会先理解你到底要解决什么问题,发现第一份资料不够,再去翻行业报告、调历史项目数据,最后给你一个完整的判断。在工程实现上,可以用 LangGraph、ReAct 框架或者其他 Agent 编排框架来做控制流,把检索器、重排器、大模型、外部工具都编排成一个可循环的过程。
5.3 GraphRAG、Ontology RAG 与知识割裂问题
纯向量 RAG 还有一个隐形问题:它把每段文本孤立看待,切出来的 chunk 之间没有任何关联,回答不了“实体之间的关系”类问题。比如“这个故障会不会影响数据采集模块”,知识库里有故障描述和数据采集模块的独立文档,但没有一段话同时提到两者,向量检索就很难把两段知识串联起来。
这就是常说的“知识割裂”问题。RAG 解决的是“模型不知道私有知识”,但没解决“知识之间没有关系网”。GraphRAG 的思路是在文档里抽取实体和关系,构建知识图谱,检索时不只查片段,还查实体之间的关联路径,从而支撑多跳问题。Ontology RAG 再进一步,把知识图谱上加上本体层/概念体系,让 Agent 能按业务逻辑去推理,而不是只做文本匹配。与之类似的思路还有 LLM Wiki:把知识沉淀成结构化的、互相链接的 Wiki,让 Agent 在回答时先找到领域入口,再细化到具体条目。这些本质上都是在 RAG 外面加一层“知识组织层”。
5.4 生产落地与 RAG as Service
真正把 RAG 部署到生产环境,要面对的事情比 demo 多得多。权限隔离是第一个问题,不能让所有用户共享同一个知识库,至少要按团队或租户做数据隔离。数据更新也是坑,文档改了之后要能增量更新,而不是每次全量重建索引。还有可观测性,要能查看某次回答命中了哪些文档、走了哪些步骤,不然线上出了问题无从查起。
行业内已经开始把 RAG 能力沉淀为“RAG as Service”,也就是把知识接入、切片、索引、检索、重排都做成标准 API,上层 AI Agent 只需要调用服务接口。这样可以避免每个项目都重新写一套管道。我在企业级 AI Agent 应用平台上看到的一个趋势是:RAG 不再只是一个库或框架,而是平台级的基础设施。对于 Java 技术栈的团队,Spring AI、LangChain4j 这类封装能帮助把 RAG 嵌入统一的应用平台;对于需要和 ERP、产品主数据打交道的场景,RAG 往往还要对接已有系统的 API,而不只是读静态文档。
6. 常见问题与排查实录
6.1 检索不到内容,先检查这四步
很多“模型回答不对”的问题,根源其实是“压根没检索到”。我排查时会按下面顺序走,每步都能过滤掉一批问题。
第一步看分块。把用户的问题拿到知识库里人工搜一遍,看看答案是不是被切碎在了不同 chunk 里。如果答案内容跨了两个 chunk,或者关键句刚好在分割线上,模型就算拿到了也不完整。第二步看 Embedding 模型。确认查询时用的模型和索引时用的模型完全一致,包括本地模型路径、版本、参数,不一致会出现“检索结果乱飞”的现象。第三步看 Top-K。K 设太小可能把正确答案挤掉了,K 设太大又会有太多噪声,建议从 4 到 6 开始调。第四步看 Metadata 过滤条件。别小看这个,我遇到过线上环境因为日期过滤条件写死,导致所有近期文档都搜不到的情况。
6.2 回答质量差,问题通常出在生成环节
如果确定检索结果里已经有正确答案,但模型还是答偏了,那问题多半在生成侧。最常见的错误是提示词里没有明确“只依据资料回答”。模型一旦觉得资料不够,就会自动用预训练知识补全,补出来的内容看着合理,实际上没有依据。好的做法是在 Prompt 里写死边界:“只能使用参考资料回答;如果资料不足,直接回答无法判断;回答时尽量引用资料原文中的关键句。”
第二个常见问题是把整个原文不做筛选地塞给模型。有些团队担心漏信息,把 Top-10 的全部内容都放进上下文,结果模型被无关资料干扰。先重排,只保留最相关的 3 到 5 条,通常比塞一堆相关内容效果更好。第三个问题是模型需要引用来源时,Prompt 里没有给出 chunk 的 metadata 信息。我给每个 chunk 都保留文档标题、页码和产品线标签,生成时要求模型在结论后面标注来源,比如[产品手册-v3.pdf 第12页],这对企业场景非常重要。
6.3 性能与成本优化笔记
用户量上来之后,性能优先考虑缓存。完全相同或高度相似的问题,可以直接走缓存,不用重新检索和生成。其次是向量索引参数,Chroma 默认配置能跑 demo,但上了百万级向量后,需要调 HNSW 的M和ef_construction,这些参数直接影响检索速度和精度。再就是 Embedding 计算,可以批量编码而不是一条条调接口;如果用开源模型,可以考虑量化或上 GPU 推理,吞吐量差别很大。
成本控制方面,把“检索”和“生成”分开看。检索阶段用小模型就能完成,生成阶段再用强模型。有些简单查询甚至不需要生成,直接返回检索到的最佳片段就行。还有一个容易忽略的成本点是分块重复索引:同一个文档被多次加载到向量库,会产生大量重复向量,既占空间又干扰检索。入库前要做去重,或者至少维护一个文档版本表。
6.4 经验速查表
| 关注点 | 建议做法 | 原因 |
|---|---|---|
| 文档加载 | 区分扫描件、表格、复杂版式 | 避免关键信息在解析时丢失 |
| 分块 | 按结构优先,配合 10%-20% 重叠 | 兼顾语义完整度和检索精度 |
| Embedding | 中文优先 bge 系,换模型重建索引 | 向量空间一致性决定检索质量 |
| 检索 | 向量 + BM25 混合,必要时重排 | 语义匹配和精确匹配互补 |
| 提示词 | 明确“资料不足就直说” | 压制幻觉,让模型保持克制 |
| 溯源 | 保留 chunk 的文档元数据 | 回答可追溯,便于定位问题 |
| 权限 | 按租户/团队隔离向量库 | 防止越权读取知识 |
| 更新 | 增量更新而不是全量重建 | 节省成本,保证数据新鲜度 |
最后再分享一个我个人的习惯。每次做一个新的 RAG 项目,我不会一上来就追求复杂架构,而是先花一天时间把最小的端到端管道跑通:一份文档、一次检索、一次回答。跑通之后再做评测、看 badcase、逐步优化。RAG 的坑几乎都藏在真实数据里,只有先把管道完整走一遍,你才能知道自己的问题到底出在解析、分块、检索还是生成。后续如果要把这个管道升级成 Agentic RAG,也别忘了先把基础管道的指标拿在手里,否则你根本说不清楚到底是 Agent 的规划变好了,还是单纯检索变强了。