做Agent开发这半年,我最大的感受是:Agent的能力边界,多半不取决于那个模型有多强,而取决于它能不能在正确的时间拿到正确的信息。模型再聪明,遇到一个没见过的私有领域问题,也只能一本正经地编个答案给你。所以这篇笔记,我来把RAG(Retrieval-Augmented Generation,检索增强生成)从建库、检索到生成的完整链路拆开讲一遍。
这篇内容适合正在做Agent开发、手头恰好有私有知识想让Agent"懂"的朋友;如果你只是听过RAG这个词、好奇它到底怎么work的,也能往下看。我会用大白话讲清楚三个环节里每个关键决策背后的理由——不是简单地"告诉你参数是多少",而是"为什么是这个参数、换掉会怎样"。全程没有炫技,都是我实际跑过的方案和踩过的坑。
RAG本质上就是一个三段式流程:先把文档变成可检索的索引(建库),再针对用户提问捞出最相关的片段(检索),最后把片段和问题一起交给大模型生成答案(生成)。听起来简单,但每个环节里都藏着足够多的细节,任何一个地方做得糙,最后出来的答案质量都会打折扣。
1. 建库之前,先把"为什么用RAG"想清楚
1.1 为什么不是直接微调模型
很多人一上来就喜欢问:我能不能直接微调一个模型,让它记住我的业务知识?我的回答通常是:如果知识量不大、更新频率低、对可解释性没要求,微调确实可以;但一旦进入真实生产环境,RAG的优势会迅速碾压微调。
直接微调至少有三个绕不过去的问题。第一是成本,无论用开源模型还是API微调,都要准备训练数据、算力资源,动辄几万条样本起步,小团队根本玩不转。第二是更新,业务文档今天改了一版合同模板、明天换了一套产品手册,微调模型就得重新训练,这个更新周期没人忍得了。第三是幻觉,微调本质上是在"背知识",模型会把背过的内容和它自身预训练的参数搅在一起,你很难分清楚它什么时候在引用你的资料、什么时候在自由发挥。
RAG把这些问题全部绕开了。文档更新了,重建索引就行,不用碰模型权重;答案生成时,每个输出都能追溯到原始文本片段,出了问题可以查来源。这套"外挂知识库"的思路,在实际业务里远比让模型"记住"更可靠。
1.2 Agent 为什么需要 RAG 这个外挂
放在Agent开发这个语境下,RAG更像是一个"记忆外挂"。Agent的核心能力是感知、决策、行动,但它的感知对象通常是用户输入和工具返回结果。如果Agent要处理的是像企业内部的运维文档、商品资料、法规条款这类外部知识,它自己是记不住的,必须有一个途径去获取这些信息。
实际开发里我习惯把RAG封装成一个标准的tool给Agent调用。Agent先判断用户问题里是否涉及私有知识,如果需要,就走"检索-生成"这套管线;如果不需要,直接让模型回答。这比把RAG硬塞进Agent的system prompt里要干净得多——否则一次对话里塞几十篇文档,还没开始干活上下文就爆了。
2. 建库环节:决定检索上限的第一道关卡
建库管的是"有没有值得检的东西"。这一步如果做不好,后面检索和生成再怎么调都白费,因为它直接决定了信息能不能被有效召回。
2.1 文档清洗比你想的更费功夫
建库的第一步不是切分,更不是embedding,而是把原始文档变成干净的纯文本。
我实际处理过的文档类型里,最省心的是Markdown和纯文本,几乎不需要预处理;PDF是最麻烦的,尤其是扫描件。扫描版PDF必须先过OCR(光学字符识别),推荐用PaddleOCR或者Tesseract,PaddleOCR对中文表格和公式的支持更友好,实测下来识别准确率能到90%以上。文字的PDF不要盲目OCR,先尝试用pdfplumber或PyMuPDF直接提取,速度快而且不会引入识别噪声。
Word文档用python-docx抽取段落时,要注意表格内容,建议把每个表格转成一行带分隔符的文本,比如"字段名:值;字段名:值"这种格式,否则向量化的时候表格语义容易被冲散。HTML文档要用BeautifulSoup去掉script和style标签,只保留正文文本,很多网页模板的导航、广告、版权声明都是噪声,不清掉会污染后续的向量。
还有个容易忽略的点:清洗时保留文档结构信息。一级标题、二级标题、列表这些标记,建议在切分前保留,或者转成带层级标识的文本。这样后面切分时可以把标题一起拼进去,检索时能拿到更好的上下文线索。
2.2 切分策略:chunk size 和 overlap 的取舍
清洗完文本,接下来就是切分(chunking)。这一步的目标很简单:把长文档切成若干个小片段,每个片段语义尽可能完整,同时体积要适合embedding和后续检索。
切分有三条路线可以选:
- 固定长度切分:按字符数或token数硬切,比如每512个token一段。实现最简单,但容易把一个完整段落拦腰截断,造成语义不连贯。
- 结构感知切分:按标题、段落、句子边界切,天然保持语义完整性,适合Markdown和结构清晰的文档。
- 语义切分:用模型判断句子之间的语义边界,质量最高,但计算成本也高,中文场景下实测效果与结构切分差距不大,不推荐优先尝试。
关于chunk size的经验,我给一个可以直接抄作业的起点:中文场景下,chunk控制在400到600个token之间比较合适,overlap设50到100个token。这里说的token是指模型的tokenizer切词结果,中文字和token的换算大致是1.5到2个字符对应1个token,不同模型略有出入。
为什么要overlap?因为相邻的两个chunk如果完全没有重叠,一个完整句子的后半句可能落到下一个chunk里,检索时只召回前半句,信息就是残缺的。我见过因为没设overlap导致问答效果骤降的情况,加了overlap之后答案质量明显回升。
不过,chunk大小不能一概而论。如果文档本身是操作手册、合同条款这种一条是一条的内容,小chunk更合适,200到300个token,因为每一条本身就是一个完整语义单元。如果是技术方案、论文这种大段论述型文档,稍大一点的chunk反而效果更好,因为检索出的上下文更完整。
2.3 向量化:embedding 模型选型要慎重
切分完成,下一步是把每个chunk转成向量。这里有几个关键点要讲清楚。
首先是维度。以OpenAI的text-embedding-3-small为例,默认维度1536;国产的bge-large-zh-v1.5是1024维,bge-m3是1024维。维度越高不代表效果越好,维度低的模型如果训练质量好,检索效果照样能打,而且占用存储和计算资源更少。选型时优先看模型在中文语义检索任务上的表现,比如C-MTEB榜单,再考虑维度。
其次是模型一致性。建库时用什么模型做embedding,检索时就必须用同一个模型,不能建库用bge、检索换OpenAI,两种向量分布根本不在一个空间里,相似度计算会完全失灵。这个错误我见过不止一次,属于最隐蔽的坑。
再次是输入长度限制。不同embedding模型的max sequence length不一样,bge系列通常支持512个token,OpenAI的text-embedding-3-small支持8191个token。如果你的chunk超过模型上限,需要截断,但截断会丢信息,所以回头调整chunk size比硬截断更合理。
我的选型建议:要求数据不出内网,用bge-m3;想省事且追求中文效果,用OpenAI text-embedding-3-small;对多语言有要求,也优先bge-m3。如果跑本地方案,我实测bge-large-zh-v1.5的中文效果很稳,配合FlagEmbedding框架加载也很方便。
2.4 向量数据库选型与元数据设计
向量数据库的作用就是存向量、算相似度。市面选择很多:Chroma、Milvus、Qdrant、pgvector、Elasticsearch 8.x。
我的建议是按部署场景来选。个人项目、快速验证,用Chroma,它就是一个嵌入式库,pip install完直接能用,Python端到端跑通非常快;生产环境、数据量大,用Milvus或Qdrant,Milvus在百万级向量上的检索性能很稳,Qdrant的Rust底层单机部署也很轻量;如果你的公司本来就有PostgreSQL,直接用pgvector,省一套新组件,但要评估并发和索引性能。
元数据设计这块,很多人不重视,但我强烈建议在建库时就把每条chunk的元数据字段想清楚。最少要有四个字段:来源文档ID(doc_id)、文档标题(title)、原始章节路径(section_path)、页码或段落编号(chunk_index)。这些字段在检索后有大用:生成阶段可以让模型把答案关联到具体章节标题和页码用于引用,排查问题的时候也能精确回溯到是哪个文档哪一段带偏了答案。
向量索引的配置也有讲究。Milvus和Qdrant默认用的是HNSW算法(一种基于图结构的近似最近邻搜索算法),建索引时报参数。以HNSW为例,核心参数M值控制每个节点的连接数,经验值是16到64,M越大召回越准但内存占用越高;efConstruction影响建索引精度,一般取200到500。这些参数在千万级向量以下场景很难感受到差别,不用过度调优,但M=32、efConstruction=200是稳妥的起步配置。
3. 检索环节:召回率与命中率的博弈
建库只是把知识存好了,真正决定用户体验的是检索环节。检索质量的核心衡量指标有两个:召回率(Recall)和命中率(Hit Rate)。召回率要看"用户问题相关的文档片段有没有被捞上来",命中率更严格,它要求相关片段出现在检索结果的前N条里。检索环节的每一个决策,本质上都是在提高这两个指标。
3.1 相似度计算:余弦、内积与欧氏距离
向量检索的原理不复杂,就是把用户问题的embedding向量和库里所有chunk的向量算距离,取最近的N个。距离度量有三种常见方式:
- 余弦相似度:看两个向量的方向是否一致,不考虑长度,最常用,对文本语义匹配效果好。
- 内积:受向量长度影响大,适合对向量做完归一化处理的场景,很多向量数据库默认用内积。
- 欧氏距离:看向量空间里的直线距离,对长度敏感,维度高时直觉上不如余弦直观。
大多数情况下,我建议用余弦相似度。但有一个细节要注意:如果embedding模型输出的向量模长本身没有意义(多数模型都是这样),余弦就是最稳的选择;如果模型明确做了归一化,内积和余弦在数学上是等价的,选哪个都一样。在Milvus里建collection时,field类型配成VECTOR_FLOAT32,metric_type选COSINE就行。
3.2 设置合理的 top-k,并持续观察命中率
top-k代表每次检索返回多少条候选chunk。这个参数需要平衡:太小容易漏掉真正相关的片段,太大则会把噪声带入上下文,既拉低答案精度又浪费上下文窗口。
经验起点是top-k=5到10。然后根据命中率反过来调:如果前5条里经常没有答案,就把k调大到10或者15;如果前5条每次都能命中,可以试着缩小k,用更少但更准的上下文换生成质量。我实际跑一个产品FAQ场景时,top-k从3调到8之后,答案完整度有明显提升,但调到15之后答案开始引入无关信息,最后落在8。
这里要提醒一句:top-k是向量召回阶段的粗筛,千万不要指望这一步一步到位。更科学的做法是召回多取一些(比如20条),然后用重排序模型精排,取前3到5条进上下文。这套"粗召回+精排序"的组合拳比单纯调小top-k效果稳定得多。
3.3 混合检索:不用傻乎乎只怼向量
纯向量检索有一个天然盲区:它擅长语义相关,却不擅长精确匹配。用户如果搜"VAT税率15%"而你文档里写的是"增值税税率15%",向量大概率能兜住;但如果用户搜的是"合同编号:HT2024001",向量检索往往会找出一堆语义相近但完全没有包含这个编号的片段,因为embedding会把这种短标识符的含义稀释掉。
这时候就要上混合检索。方案是BM25(稀疏检索)与向量检索(稠密检索)并行执行,BM25负责精确词汇匹配,向量负责语义匹配,再把两路结果融合。融合算法我推荐RRF(Reciprocal Rank Fusion,倒数排名融合):对每路结果按排名给分,取倒数求和,简单有效,实测比加权分数融合更稳。
具体来说,文档先同时建立两个索引:一个是向量索引,一个是BM25文本索引。检索时,Query先走向量库拿到相近的20条,再走BM25拿到精确匹配的20条,然后RRF融合,TopN进下游。ES 8.x原生支持这种混合检索,向量和BM25可以在一个索引里配置,非常方便。Elasticsearch如果版本在8.8以上还支持rank_features,不过能不能用取决于运维的接受程度。
3.4 从意图识别到检索语义:Query改写这一步不能省
用户真的会把问题问得很随意,尤其是多轮对话时。用户上一句说"我看了你们的入门指南",下一句直接问"第二步的那个参数怎么配"——这里的"那个参数"如果不改写,直接拿去检索,几乎什么都捞不到。
所以检索之前,要先做一次Query改写(Query Rewriting),把原Query变成更适合检索的语句。常见做法有两个:
一是补充历史上下文:把这轮问题、上几轮用户问题、Agent的回复拼接,让LLM提炼出独立的、语义完整的检索Query。这个方案灵活但多一次LLM调用,延迟会增加几百毫秒到一个秒级,生产环境要权衡。
二是对Query做纯规则清洗:去掉语气词和无关表达、识别并抽取关键实体。如果用户问题里带有文档标题、编号等强标识信息,要拼接进改写后的Query里,保证精确匹配能命中。
我建议不要省略这步。用LLM改写一次Query的开销并不高,但对召回的提升非常明显。有个经验数据:在具体业务里,做了Query改写之后,hit rate能从65%左右提升到80%以上,这个提升幅度相当可观。
3.5 重排序:让最相关的片段排到最前面
重排序(Rerank)阶段,业界主流的做法是用cross-encoder结构的模型。它把Query和每个chunk拼接成一对,同时输入模型判断相关性分数,相比双塔式的向量检索,跨编码器能让Query和文档深度交互,相关性判断精确得多。
推荐两个模型:bge-reranker-v2-m3(BAAI出品,中文效果稳定),以及Cohere Rerank(API方式,英文效果很强)。重排序模型输入有长度限制(通常512个token),所以重排序前要先确认chunk没超长,超了要截断。
重排序策略是:向量检索召回20到30条候选,重排序后只保留前3到5条进入生成。我实测过不加重排、纯靠向量top-k进生成的效果,再用bge-reranker重排对比,答案质量提升非常明显——重排后答案内容更聚焦,不再出现东拼西凑的"缝合感"。
4. 生成环节:把检索片段变成高质量答案
建库和检索配合好了,最后一步就是让大模型根据检索结果生成答案。很多人的理解是"把检索结果拼到Prompt里再问一遍模型",但实际做起来,细节比这多得多。
4.1 Prompt 模板设计:先把约束说清楚
生成环节的Prompt设计,我总结了一个核心原则:把检索结果当作"论据",而不是"答案抄写材料"。也就是说,模板里必须明确告诉模型——只依据提供的上下文回答问题,如果上下文里没有相关信息,直接说明不知道,不要编造。
一个经过实战验证的模板骨架是这样:
你是一个专业助手。请回答用户问题,回答时严格遵守以下规则: 1. 仅依据提供的【参考文档】回答,禁止使用你自身预训练知识中与参考文档矛盾的内容。 2. 如果参考文档中没有足够信息,请明确回复"根据现有资料无法回答"。 3. 回答中涉及的事实性信息,必须在句末标注来源编号,格式如[1]、[2]。 4. 回答要简明扼要,不要复述完整参考文档,只提取与问题直接相关的内容。 【参考文档】 [1] 来源: 产品手册.pdf 第2章 3.2节 内容: (chunk文本) [2] 来源: 设备配置说明.md 第4节 内容: (chunk文本) 【用户问题】 (改写后的Query)这里有一点要注意:给模型的参考文档一定要带上"元数据"前缀(来源、章节),而不是只给正文。这样模型在生成时能感知到不同片段来自不同地方,写引用时可以标注准确来源;否则它就只知道引用了"某段未知来源的文本",引用功能形同虚设。
还有个小技巧:在模板里把多个chunk用分隔标记隔开,比如用编号+来源+内容的结构化格式。模型对这种"再次结构化后输入"的内容理解得更好,比一坨连续拼接的文本强很多。
4.2 上下文窗口管理:别把模型撑死
RAG系统一个非常常见的崩溃方式,是把太多chunk一股脑塞进上下文。窗口再大的模型,塞进去几千行文档之后,注意力也会被稀释,模型开始"东看一句西看一句",答案质量断崖式下跌。
我的上线前自检标准:最终进入上下文的文本总量不超过模型上下文窗口的60%到70%。假如你的模型是128K窗口,给RAG用的预算大约在80K到90K以内。如果检索返回的chunk总长度已经快顶到窗口上限,有两个办法兜底——一是缩小重排序后的保留条数,二是对chunk做摘要压缩再放入上下文。
还有一个容易忽略的点:system prompt本身也是上下文的一部分。如果系统提示词写了几千字,再塞入几十个chunk,就可能超出窗口。RAG场景下,system prompt应该精简,只保留角色说明和规则,别把公司介绍、操作手册这类的"常驻知识"写到system prompt里——那是建库该干的事。
4.3 引用溯源:生成答案的最后一道防线
引用溯源不只是为了让用户"看到来源",更是一个防幻觉的有效手段。在前面Prompt规则的基础上,我要求模型每个事实点都标注来源编号,然后在代码层面做一道校验:如果答案里出现了[3],但[3]对应的chunk内容里根本没有这句话,就判定这是一次潜在幻觉,需要重新生成或让用户确认。
实现起来不复杂,核心就是解析输出中的引用编号,回到原始的chunk文本里做关键词或语义比对。这套机制在业务场景里极其有价值,因为很多用户对"AI说的对不对"非常敏感,有了可溯源的引用,整个系统的可信度直接上一个台阶。
在Agent场景里,引用信息不只是用来展示,它还能作为下一步行动的输入。比如用户追问"你引用的这个条款是哪个版本的",Agent可以根据引用里的"版本号"字段继续检索,这是纯问答做不到的——从这时开始,RAG就不是一个静态流程了,而是长出了"决策"的雏形。
5. Agentic RAG:从单轮检索到多轮决策
现在很多项目已经不满足于"用户问一次、系统检一次、模型答一次"这种固定流水线了,于是热词"Agentic RAG"出现了。它的核心变化是:检索这个动作不再是提前写死的流程,而是由Agent根据对问题的理解动态发起、动态终止、动态调整。
其实Agentic RAG可以用一句话概括:把RAG从"管道"变成"工具"。传统的RAG是一条流水线,Query进、答案出,过程不可干预;Agentic RAG则是把"检索增强"拆成了几个原子能力,让Agent像人一样判断该不该检索、该先查什么、查到的信息够不够,不够再查一轮。
5.1 从固定流程到动态决策:Agent 怎么"调用"RAG
我把Agentic RAG的决策逻辑拆成几个关键节点:
- 意图路由(Routing):先判断用户问题需不需要检索。比如"你好"、闲聊寒暄,直接由模型回答即可;涉及具体事实、参数、流程、规范类问题,才走检索。这一步用LLM做意图分类,成本低,收益大,能省下一大堆无意义的检索开销。
- 查询规划(Query Planning):对于复杂问题,拆解成多个子查询。比如用户问"怎么部署Nacos集群并配置数据库脚本",Agent可以拆成"Nacos集群部署步骤"和"Nacos数据库建库建表脚本"两次独立检索,再合并回答。这一步比分一次大检索效果更精准。
- 迭代检索(Iterative Retrieval):第一轮检索结果不够时,Agent基于已有信息生成新查询,继续检索,直到信息充足或达到最大迭代次数。实现时要注意设置轮数上限,防止死循环烧钱。
5.2 RAG 与其他工具协同:检索不是唯一的信息来源
在Agent的完整工作流里,RAG往往不是唯一的信息来源,它还与搜索、API调用、代码执行等工具一起协同。比如:Agent先通过RAG检索出某设备型号的参数,再通过另一个工具查询该设备的实时库存,最后把两路信息合并生成答案。这种"多工具并行+决策编排"才是Agent场景的正解。
实际开发中我建议把RAG封装成独立的tool服务,对外提供两个接口:一个是对文档的"写入/更新"接口,一个是对用户的"检索"接口。这样Agent侧只关心函数签名和返回格式,不需要感知向量库、embedding模型这些底层细节,后续替换模型或数据库时也不会影响Agent主流程。
5.3 进阶方向:GraphRAG、Ontology RAG 与多模态检索
多轮检索之外,RAG的进阶方向主要集中在两个:一个是引入图结构,另一个是引入多模态。
GraphRAG的思路是先从文档中抽取实体和关系,构建知识图谱,再结合向量检索进行图遍历。它在处理"多跳关系"问题上比纯向量检索强,比如"公司A和公司B共同参与的项目有哪些"这类问题,纯向量检索往往答不好,图结构可以沿着关系边一步步推理。如果你想用,可以考虑微软的GraphRAG开源项目,但要做好心理准备:构建图谱的计算成本很高,小规模知识库不太划算。
Ontology RAG比GraphRAG更进一步,它给知识图谱加了一层语义模型。构建速度更慢、成本更高,但检索结果的准确率和推理性更强,适合知识结构复杂、关系明确的领域,比如医疗药品和金融产品。对大多数团队来说,GraphRAG已经到了足够用的程度,不建议一开始就上Ontology。
多模态检索解决的是"文档里既有文字又有图、表、公式"的问题。目前成熟方案是双编码器结构,分别对图片和文本做embedding,用统一向量空间对齐,比如CLIP家族模型。但我要实话实说:多模态RAG的工程复杂度比纯文本高不少,如果你的场景里图片只是配图、文字才是关键信息,先把纯文本RAG做好,多模态作为二期再考虑,别一开始就把摊子铺太大。
6. 常见问题与排查技巧实录
最后分享一些实际运行RAG系统时遇到的典型问题。这些都是我一条条踩出来的,堪称避坑清单。
6.1 建库阶段的坑
常见问题有这么几个:
- chunk切分破坏了语义单元。症状是检索结果前言不搭后语。排查时看召回出来的片段是不是经常"一句话只说了一半"。解决办法是先按章节/段落切分,再对超长段落做二次细切,别只用固定长度硬切。
- 文档清洗不干净。症状是检索结果里频繁出现导航菜单、页眉页脚文字。检查清洗脚本是否把HTML的script、style标签和重复模板文本过滤干净,PDF提取后可以先抽样看看文本质量再入库。
- embedding模型来源混杂。症状是检索结果整体漂移,相似度分数普遍偏低。排查全链路是不是混用多个embedding模型,确认建库和检索用的是同一个。
6.2 检索阶段的坑
- 命中率低但召回率还行。先看是不是top-k设置太小,优先重排序而不是盲目扩大top-k,扩大top-k通常只会把噪声一起带进来。
- 检索结果太泛化。典型情况是Query太短,比如用户只输入"服务器"这个词。先把Query改写和实体抽取做起来,把"服务器"扩展成"服务器配置""服务器故障处理"这类带意图的检索词。
- 精确ID、编号搜不到。那就是纯向量检索的通病,尽快上BM25混合检索,别只堆向量。
6.3 生成阶段的坑
最常见的两个:
一是幻觉,上下文没有相关信息,模型硬答。解决办法是Prompt里强调"不知道就直说",同时在代码里加引用真实性校验,不满足就拒绝生成或重新检索。
二是答案"缝合感"重,一段话把多个互不相关的chunk拼在一起。排查重排序效果,看看是不是recall阶段召回的chunk本身太杂,精排没压住。另外检查top-k是否过大,千万别把20多条chunk全部塞进上下文,3到5条是合理区间。
整个RAG链路做得越久越发现:建库的底子、检索的取舍、生成的约束,每个环节都不能有短板。早期项目里,我花了很多时间在调生成部分,后来才发现问题出在建库阶段文档没清洗好、或者检索阶段没有用混合检索。先找到短板再发力,比盲目优化单点效率高得多。
最后分享一个我自己的体会:RAG不是万能的,它适合的场景是"知识可结构化、答案可溯源";如果你的问题高度依赖推理和多步骤执行,纯RAG会很吃力,那就要往Agentic RAG或者GraphRAG的方向去扩展。搭系统的时候,先把最简单的闭环跑通——小规模文档、固定流程、单轮问答——再逐步叠加Query改写、重排序、多轮决策这些进阶能力。每叠加一层,都回归测试一轮hit rate和答案质量,确认有提升再留下,没提升就撤掉。这样一步一步做出来的RAG系统,才是真正经得起业务检验的。