免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

AI Agent知识获取管道:RAG从原理到实践

AI Agent知识获取管道:RAG从原理到实践 最近在带一个 AI Agent 的实战项目正好推进到第四篇这次聊聊知识获取管道也就是 RAG。如果前三篇我们解决的是 Agent 的“大脑”和“手脚”那这一篇解决的就是 Agent 的“眼睛”和“耳朵”——它怎么拿到自己不知道的信息。很多人对 RAG 的理解停留在“给大模型接个知识库”但真正落到 Agent 场景里RAG 远不止“检索 拼接 Prompt”这么简单它是一条完整的管道牵涉到文档切分、向量检索、相关性排序、上下文组装甚至还要跟 Agent 的工具调用、多轮记忆进行联动。这篇文章我会从零开始拆解 RAG 的基础原理然后给出一套可以照着抄的最小实现最后把我实操中踩过的坑和排查思路一并整理出来。内容偏向落地实践适合正在做 AI Agent 开发、想给自己项目接知识库的工程师也适合刚接触 RAG、想知道它到底是什么、怎么用、坑在哪里的初学者。1. 为什么 AI Agent 需要知识获取管道1.1 模型再大也有知识边界先明确一个问题大模型的知识是“训练时”的不是“运行时”的。一个模型在预训练阶段见过的数据决定了它默认会什么、知道什么。这意味着无论模型参数多大它对以下三类信息天然是空白的企业内部文档、特定领域的私有知识、以及训练截止日期之后才出现的新信息。很多人误以为“模型不知道我就把资料塞进 Prompt 里让它读”这在小规模场景下可行但在真实项目里几乎走不通。一个 Agent 可能要面对几百份文档每份几千字全塞进去不仅会超出上下文窗口还会让模型在小细节里迷失回答的稳定性会急剧下降。我自己实测过把大量原文直接塞进 Prompt模型的“幻觉率”反而会上升因为它分不清哪些信息是用户给的、哪些是它自己“脑补”的。RAG 的思路是反过来的不让模型读所有资料而是先通过检索把“最相关的几段”找出来再把这些片段作为参考材料交给模型。模型不需要知道全部只需要基于你给它的那几段材料作答。这就把问题从“模型记住了多少”变成了“检索找得准不准”后者是完全可控的工程问题。1.2 Agent 场景下的三大知识痛点AI Agent 相比普通聊天机器人多了任务拆解、工具调用、多步决策这些能力这也让它的知识获取变得更加复杂。我在项目中总结出三个独特的痛点单靠普通问答式 RAG 是不够的。第一个痛点是“知识时效性”。Agent 在执行任务时可能需要实时查询产品价格、库存状态、最新的政策文档。这些信息每天早上都会变。如果只是提前把资料导入向量库那 Agent 回答的还是“昨天的知识”。所以 Agent 场景下的 RAG 管道必须支持增量更新甚至要能按需触发检索而不是一次性灌入就完事。第二个痛点是“多步任务里的知识拼图”。Agent 的任务很少是“问一个问题给一个答案”。它可能需要先查用户是谁再查用户的订单再根据订单查物流规则最后综合所有信息给出结论。每一步都是一次知识获取而且后一步依赖前一步的结果。这意味着 RAG 在 Agent 中不是孤立的检索接口它要和状态管理、工具调用、记忆模块协同工作。第三个痛点是“知识之间的割裂”。这也是热词里反复出现的“解决了知识割裂”所指的问题。一个业务问题往往散落在多个文档里——产品说明说功能运营手册说流程售后文档说常见问题。如果每个文档独立建档、独立检索Agent 拿到的是碎片。怎么让知识之间建立关联让 Agent 在一次任务中把多个来源的信息串起来这才是知识获取管道真正要解决的深水区。1.3 管道视角下的 RAG 定位很多人把 RAG 理解成“向量检索工具”这是很片面的。检索只是管道中间的一环。一条完整的 RAG 管道至少包含五个环节知识接入、文档处理、索引构建、查询检索、结果生成。每个环节都有独立的工程决策一个环节出问题整个管道的效果都会崩坏。这也是为什么这一篇的标题强调“知识获取管道”而不是“RAG 技术”。管道思维要求你把知识从源头到模型输出看成一个完整的链路每个环节都要有监控、有评估、有兜底策略。只盯着检索一个环节就像只练传球不练射门数据流程看似跑通了但实战效果一塌糊涂。后面我会按照这个管道视角把每个环节的原理和实操拆开讲。2. RAG 管道的整体架构与设计思路2.1 三阶段架构索引、检索、生成RAG 的经典架构可以分为三个阶段理解这个划分是后面所有调优的基础。索引阶段是离线进行的核心任务是把原始文档转成模型可以检索的结构化数据。具体包括解析文档格式PDF、Word、Markdown、清理冗余内容页眉页脚、导航栏、水印、按策略切分成块Chunk、对每个块做向量化Embedding、再把向量和原始文本一起存入向量数据库。这个阶段最像“图书管理员整理书架”——书怎么分类、怎么上架直接决定了后面找书找不找得到。检索阶段是用户查询进来后触发的核心任务是把用户的提问转成向量在向量库中做相似度搜索找出一批最相关的文档块。这里有个容易被新手忽略的点检索不是“搜索关键词”而是“语义匹配”。即使用户的提问里没有文档中的任何原词只要语义相近向量检索也能找出来。这也是 RAG 相比传统数据库查询的最大优势。生成阶段是把检索结果组装成 Prompt 的一部分交给大模型生成回答。这个阶段的关键是“怎么组装”是简单拼接还是让模型先判断哪段材料有用要不要给材料标注来源要不要在回答中注明“未找到相关内容”组装策略直接影响回答质量和可信度。2.2 为什么我不建议一上来就上 GraphRAG近期 GraphRAG 和 Ontology RAG 相关讨论明显增多很多开发者在做 RAG 项目时一上来就想追求复杂的知识图谱方案。我的建议是如果你的知识库小于几千个文档块经典向量检索足够了先不要碰图谱增强方案。原因很简单。GraphRAG 的收益在于捕捉实体之间的多跳关系比如“A 产品的 B 模块依赖 C 服务的 D 配置”。这种关系确实存在很多业务场景中但构建图谱需要额外的实体抽取、关系定义、图数据库存储成本和维护难度都不低。对一个初版 RAG 项目来说你首先要证明的是“检索能命中、回答能用”然后才有必要考虑“知识间的关系网络”。从工程收益来看向量检索 重排Rerank在绝大多数场景下能够达到 80 分的效果而补齐剩下的 20 分往往要付出好几倍的复杂度。我倾向于把 GraphRAG 这类方案留到“业务验证已经跑通、知识关联确实成为瓶颈”之后再引入这才是合理的技术演进路径。同样的道理也适用于 Agentic RAG它是一种让 Agent 自主决策如何检索的进阶模式但它不是 RAG 的默认起点而是系统复杂性提升后的自然进化方向。2.3 与微调、长上下文方案的取舍做 AI Agent 知识获取时经常会被问到为什么不用微调为什么不用超长上下文直接吞文档这里给出我的对比思路。微调解决的是“模型的表达能力”让模型在特定风格、特定输出格式上表现更好通俗说就是“让模型学会说话”。但微调不太适合解决“事实型知识”问题。因为事实型知识是动态变化的今天更新了一版价格表明天调整了一条政策每改一次就微调一次成本实在太高。而且微调存在“知识注入遗忘”的问题如果微调数据里新旧知识混杂模型可能会学到旧规则。RAG 则不存在这个问题——知识更新只需要重新建立索引模型本身的参数不动。长上下文方案是今年被讨论得很多的方向模型上下文窗口从 8K 扩展到 128K、200K 甚至更大。看起来似乎可以直接把整本手册塞进 Prompt。但实测下来超长上下文存在两个现实问题一是成本每次请求都携带几万 token调用费用和时间都会明显上升二是“迷失在中间”现象模型对排在 Prompt 中间位置的信息关注度明显下降这和人的注意力规律很相似。所以长上下文适合作为兜底而不是主方案。推荐的做法是 RAG 定位精准语义块、长上下文作为临时备用缓冲区两者结合效果和成本都能兼顾。3. 实操基于 LangChain 搭建最小可用 RAG3.1 技术选型与备选方案开始动手之前先明确技术选型。我这里选择 LangChain 构建检索逻辑因为它的文档加载器和文本切分器覆盖得很全面社区资料也丰富很适合新手快速跑通全流程。如果你更偏好轻量方案直接用 LlamaIndex 也可以思路上大同小异。向量库我用 Chroma原因是它支持本地持久化、零配置启动对个人项目和小团队足够友好。生产环境可以考虑换成 Milvus 或 Qdrant性能和并发能力更强。嵌入模型我推荐使用开源的中文向量模型比如BAAI/bge-large-zh-v1.5它对中文语义的支持很不错在多数场景下能稳定工作。如果你在跑英文文档也可以用text-embedding-ada-002这类闭源 API。判断嵌入模型好坏的直观指标是“同一语义表达如‘怎么退款’和‘退钱流程’在向量空间中的距离是否足够近”你可以在选定模型后先拿几组同义句做一次相似度测试确认基本盘没问题再继续这个习惯能帮你规避很多后续排查问题的麻烦。大模型部分我假设你已有可用的 OpenAI 兼容接口或本地部署模型。下面的代码示例里我使用环境变量注入 API Key 的方式避免硬编码。3.2 文档切分的参数设计与原理切分是 RAG 管道里最容易被低估的一环。切得太碎语义被截断检索出来的片段像是拼图碎块切得太大单块里掺杂无关信息向量表示的语义会被“平均化”相关性分数也会被稀释。我在项目中常用的是“递归字符切分器”核心参数有三个chunk_size决定块的目标字符数chunk_overlap决定相邻块的重叠字符数separators决定切分优先级。推荐的起点值是chunk_size500、chunk_overlap50。这个组合的意义是每块大约容纳 300-500 个汉字既保住了完整语义又不会让向量表示的焦点过度分散重叠部分则用来照顾“边界句”——如果一句话恰好横跨两块重叠区能确保这句信息至少完整出现在其中一块中。有一种流传较广的说法“切多大取决于你的模型”这只能说对了一半。决定切分粒度的首要因素其实是“你的检索片段如何被消费”。如果你的下游模型需要读完整的表格或一段完整的操作步骤切分就应该对应到完整小节如果只是做事实性问答切成 300-500 字的小块就够用。换句话说先定义清楚“文档里什么样的一段信息算一个完整答案”再去定切分策略这样思路才顺。3.3 完整实现索引构建与检索生成现在给出一个可直接运行的实现。以下代码基于langchain和chromadb用最朴素的方式跑通链路你可以照着搭建之后在这个基础上迭代。import os from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma from langchain.prompts import PromptTemplate from langchain.chat_models import ChatOpenAI from langchain.schema.runnable import RunnablePassthrough # 加载本地文档 loader TextLoader(handbook.txt, encodingutf-8) documents loader.load() # 切分文档 text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , , , ], ) chunks text_splitter.split_documents(documents) print(f文档已切分为 {len(chunks)} 个块) # 初始化嵌入模型与向量库 embedding_model HuggingFaceEmbeddings( model_nameBAAI/bge-large-zh-v1.5 ) vectorstore Chroma.from_documents( documentschunks, embeddingembedding_model, persist_directory./chroma_db, ) # 构造检索器返回 top-4 相关片段 retriever vectorstore.as_retriever( search_typesimilarity, search_kwargs{k: 4}, ) # 组装生成 Prompt prompt_text 你是一个基于知识库的问答助手。 请仅根据以下资料片段回答问题。如果资料中没有相关信息请明确说明“知识库中未找到相关内容”。 资料片段 {context} 问题{question} 回答 prompt PromptTemplate.from_template(prompt_text) # 构建生成链路 def format_docs(docs): return \n\n.join(f[来源 {i1}] {doc.page_content} for i, doc in enumerate(docs)) llm ChatOpenAI( model_namegpt-4o-mini, api_keyos.getenv(OPENAI_API_KEY), temperature0.2, ) rag_chain ( {context: retriever | format_docs, question: RunnablePassthrough()} | prompt | llm ) # 测试 query 如何申请退款 response rag_chain.invoke(query) print(response.content)跑通这段代码后你已经具备了一个最基础的知识获取管道。但这个版本还比较粗糙它只是把检索结果拼进 Prompt没有处理“检索结果不好怎么办”的情况也没有考虑多轮对话中的上下文。下面是几个关键的进阶环节。3.4 检索策略与重排从召回走向精确上面对话链路中最隐蔽的缺陷是直接使用向量相似度作为唯一排序依据而向量相似度并不总是等同于“真实相关性”。有一种常见情况用户的问题和某个文档块提到“退款”向量距离很接近但文档块真正在讲的是退款政策附件而不是退款流程这就会产生“语义上沾边、实际上答非所问”的检索结果。解决这种问题的方案是引入重排Rerank环节。基本思路是先用向量检索召回一批候选结果比如 top-20再用一个更精细的交叉编码器模型对每个“问题-文档块”对做一对一的相关性打分最后取分数最高的 top-3 进入生成。交叉编码器能同时看到问题和文档块的完整文本因此判断准确性远高于单纯向量比对。用组件表达你可以在 LangChain 中这样增加重排# 伪代码示意重排环节的插入位置 from langchain.retrievers import ContextualCompressionRetriever from langchain.retrievers.document_compressors import CrossEncoderReranker compressor CrossEncoderReranker( model_nameBAAI/bge-reranker-base, top_n3, ) compression_retriever ContextualCompressionRetriever( base_compressorcompressor, base_retrieverretriever, )重排不是必须的。如果你的知识库很垂直、文档之间差异很大、用户问题相对固定那向量检索已经足够。但如果用户问题开放度高、文档之间存在大量相似表述重排带来的提升会非常明显。我的习惯是先不做重排跑通基线再用测试集评估命中率如果 hit rate 不达标再引入重排这样可以量化每一步的收益。3.5 关键参数速查表我把实操中用得最多的配置参数整理成一张表方便你直接参考。注意这只是起点每个项目的理想参数都需要通过测试来收敛。参数推荐起点值调整方向影响效果chunk_size500文档内容细碎则调小至 200-300内容连贯性要求高则调大至 800太小丢失语义太大稀释焦点chunk_overlap50与 chunk_size 成正比例通常为 10%-20%防止边界句子被截断检索召回数 k4-6答案依赖多点材料时可上调提高召回率但引入噪声重排后保留数3减少噪声、控制 token 成本在召回和精确之间取平衡嵌入模型维度1024bge 系列与模型配置对齐即可影响存储占用和比对精度temperature0.2事实型问答调低至 0-0.1降低自由发挥导致的幻觉重要提醒调参必须配合评估集。建议准备 20-30 组“问题-标准答案片段”作为测试集写脚本批量跑检索统计 hit rate问题对应的标准片段能否出现在召回结果中。没有评估调参就是盲调永远是在碰运气。4. 常见问题与排查技巧实录4.1 检索不到先别急着调模型做 RAG 时最常遇到的困境是用户问了一个问题模型回答“知识库中未找到相关内容”但你明明知道知识库里就有答案。这时候我的排查顺序是固定的从上往下依次查。首先检查切分是否正确。如果原始文档里“退款分为原路退回和退回余额两类场景”这句话恰好被两个块切开每一半单独检索可能都无法命中“退现金”这个提问意图。解决方法是调整chunk_overlap或者干脆按结构化标题Markdown 标题、PDF 中的章节标题切分。然后检查嵌入模型是否适合你的文档领域。通用中文嵌入模型对口语化提问的适配度通常不错但如果你处理的是编程代码、医疗术语、法律条文这类强领域数据建议测试领域微调过的嵌入模型效果往往比通用模型好一截。还要检查检索结果本身。我给的一个建议是把向量检索返回的 top-5 片段和对应相似度分数直接打印出来人工看一遍。这一步能帮你快速区隔“检索没找对”分数普遍低还是“检索找对了但模型没用好”分数高但回答依然答非所问。两个问题完全有不同的处理路径。4.2 切分粒度与命中率的博弈这节算是我踩过最深的一个坑当时做一个产品手册知识库最初把chunk_size设为 1000觉得块越大信息越全结果 hit rate 只有不到 60%。排查后发现问题出在很多文档块里包含多个主题开头讲产品 A 的参数中间提到产品 B 的兼容性结尾又回到产品 A 的售后。向量表示把整块的语义“平均”了导致任何一个单独主题都匹配不准。后来我把chunk_size调到 300并增加按标题和段落边界的切分优先级hit rate 提高到 85% 以上。这让我意识到一个重要的原则切分的核心不是“让每块尽可能多”而是“让每块尽可能纯”。一块文本只表达一个主题检索时才能精准命中。另外要提醒的是不要机械地按字数切分。Markdown 文档天然有结构优先按标题和列表切表格要保持完整技术文档可以按“步骤编号”切每个步骤一个块问答型知识库可以按“问题-答案对”切。结构化切分的效果通常优于定长字符切分这是低成本高收益的优化手段。4.3 多轮对话里的查询重写真实 Agent 场景中用户很少一次给出完整的问题。常见的模式是“帮我查一下产品 A 的价格”“那 B 呢”“两个产品的套装打几折”。第二句和第三句都是指代性的省略表达直接拿原文去做向量检索效果会很差。解决方案是做一个“查询重写”步骤。在进入检索之前先把用户当前提问 最近几轮对话历史一起交给一个轻量模型让它改写成一个“独立可检索的完整查询”。比如用户问“那 B 呢”时上下文是“产品 A 的价格”改写结果应该是“产品 B 的价格是多少”。LangChain 中利用HistoryAwareRetriever就可以比较方便地完成这一步它会用一个 LLM 判断当前提问是否需要依赖历史需要的话就生成新的查询再进行检索。我这边的切身体会加了这个环节之后多轮对话场景的检索命中率提升了非常明显可以说是 Agent 场景 RAG 落地中最值得投入的一个增强点。4.4 Agentic RAG检索策略本身也交给 Agent写到这接近本文收尾正好也是热搜词里反复出现的“Agentic RAG”。这个方向的核心思想很直接传统 RAG 的检索流程是固定的用户提问后系统无条件执行一遍“查询改写-向量检索-拼装-生成”。但在真实 Agent 场景中检索有时是多余的有时需要多路并行有时还需要先查数据库再查文档。把这些策略性决策交给 Agent 本身就是 Agentic RAG。举一个我正在开发的客服 Agent 设计样例。用户说“帮我退掉今天买的保险”。Agent 分析主任务后会规划出三个子动作先从用户订单系统查询这笔保单是否存在再从保险产品知识库检索“退保规则”最后从售后政策库检索“特殊退保条件”三次检索并行执行最终综合结果生成回答。这不是“一次检索召唤分片”而是引擎根据任务动态决定要查哪里、查什么、查到什么程度。落地 Agentic RAG 最实际的方式是把 RAG 封装成一个可被 Agent 调用的工具而不是在代码里写死检索流程。你可以用 LangChain 的create_openai_tools_agent或自定义工具函数让 Agent 自主决定何时调retrieve_from_knowledge_base、何时走数据库查询、何时结束。封装成工具也带来了显著的可扩展性——你可以像搭积木一样接入网上的各种检索服务这也是当前 RAG 落地的演进方向之一。最后分享一个小经验。RAG 项目做得多了会发现百分之八十的“效果不好”并不是模型不够聪明也不是向量库不够先进而是输入侧的数据质量出了问题——文档格式混乱、切分不讲结构、检索没有评估。与其追新框架新技术不如先把管道每一环的数据质量攥扎实。定好测试集、量化指标、边跑边看检索结果工程上把基础环节都做对了出现什么样的幺蛾子你手里都有牌可打。
返回列表