news 2026/9/28 15:35:25

RAG实战指南:从切分、嵌入到检索,搭建Agent知识获取管道

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
RAG实战指南:从切分、嵌入到检索,搭建Agent知识获取管道

搞AI Agent,绕不开RAG。做企业知识库问答、私有文档助手、产品文档客服,只要想让Agent回答"模型记不住的内容",RAG就是目前性价比最高的一条路。这篇是《走进AI Agent》系列的第四篇,前几篇我们聊了Agent的规划、记忆和工具调用,这一篇轮到知识获取管道:RAG到底是什么,管道里每一环为什么这样设计,一个最小可用系统怎么搭,以及我在实战里踩过哪些坑。适合准备给项目接知识库、或者想从0到1搭一个Agent的同学参考。

1. Agent没有RAG,就像闭卷考试:知识获取管道到底解决什么

1.1 Agent的短板不是推理,是"记不住"

大模型参数里装的是公开语料训练出来的压缩知识,有两个天然局限:一是知识截止日期固定,二是私域信息完全空白。Agent是要替你办事的系统,办事就得基于事实。比如用户问"我们公司的年假政策",模型参数里根本没有这份制度,它只能凭通用常识瞎猜。再比如产品客服场景,用户问"GTX-1080的供电接口是几pin",老版本参数表在训练语料里可能有,但新品的参数它一定不知道。

所以我在给团队讲Agent架构时,常说一句话:没有RAG的Agent,相当于闭卷考试。闭卷能答对的问题,全部是模型训练时见过的内容;凡是私有的、时效的、小众的知识,闭卷必然翻车。RAG就是给Agent发一张"开卷资格",考试时先翻书找到相关知识点,再动笔答题。翻书不是把整本书背下来,而是按题目定位到最相关的几段,这种"先检索、再组织、后输出"的管道,就是RAG。

1.2 RAG解决三个具体痛点

一、时效性问题。模型训练完成后,知识就冻结了。产品上线了新版本、政策出了新规定,模型不会自动知道。RAG不需要重新训练,把新文档扔进知识库重新索引即可,更新成本极低。

二、私域性问题。企业内部的SOP、项目文档、ERP里的商品信息、个人知识笔记,这些内容不在任何公开训练语料里,无论模型多大都"记不住"。RAG把私有文档变成可检索的切块,Agent就能基于这些私有资料来回答。

三、幻觉可控性问题。大模型生成时容易"一本正经地胡说八道",尤其是在知识密集的领域。RAG不是消灭幻觉,而是把生成过程绑定在检索到的资料上。虽然模型仍可能发挥,但至少它能引用出处,幻觉一旦出现就能被追查和纠正。这三个痛点没有RAG时几乎无解,要么花钱做微调,要么接受Agent满嘴跑火车。

所以RAG在AI Agent中的地位,有点像人的图书馆检索系统——看起来只是工具,实际上是整个认知能力的底层支撑。Agent能不能"靠谱办事",一半取决于推理能力,另一半取决于它能不能拿到准确的参考资料。

1.3 先RAG还是先微调:我的判断标准

很多新手一来就问"要不要微调",我通常会反问三个问题:

  • 场景类型:知识密集型问题(事实、条款、参数、规格)走RAG;行为风格类需求(语气、格式、特定领域话术)走微调。
  • 知识更新频率:更新快走RAG。换一份文档重新索引就行,微调一次训练成本高,不适合频繁迭代。
  • 资源门槛:RAG不需要GPU训练环境,检索加生成的主要开销在推理;微调要准备训练数据和算力,起步成本高。

但这俩不是非此即彼。我落地过一个客服Agent,FAQ类问题全走RAG,从知识库拿准确事实;回复话术的温和感、品牌语气,用微调小模型实现。两条腿走路,才能在成本和效果之间找到平衡点。如果你手头预算有限,我建议先只上RAG,跑起来看效果再决定要不要补微调。

2. 拆开RAG管道:切分、嵌入、检索这三关怎么过

2.1 切分:检索效果的上限,在进库那一刻就定了

新手最容易犯的错,是觉得"把整本手册扔进去,Agent就能查了"。真不行。大模型上下文有限,检索系统也不可能拿全文比对。RAG第一步,就是把文档切成语义相对完整的片段(chunk),后续所有检索都发生在这些片段上。

切分方式分两类。

第一类,结构性切分。按Markdown标题、PDF章节、代码的函数或类边界来切,每个片段自带语义边界。比如操作手册里"重置密码"这个二级标题下面的内容,天然是一块完整知识,按标题边界切,检索命中时上下文就是完整的。

第二类,固定大小切分。用RecursiveCharacterTextSplitter这类工具,按字符数分块,同时配一个overlap。overlap是相邻块之间的重复内容,它的作用就是防止同一句话在边界处被拦腰截断。如果没有overlap,检索到的片段可能是"打开设置界面"这半句,后半句"进入安全中心点击重置"被切到下一块去了,模型拿到信息残缺,答案自然残缺。

举一个我实际处理过的例子:某产品操作手册,最初按固定800字切,用户问"如何重置密码",召回结果里只有前半句,找不到关键操作步骤。把chunk_size调到500、overlap调到100后,边界切得更合理,问题完整命中。切分粒度没有绝对标准,不同文档类型差别很大:

  • 产品文档、操作手册:以标题结构为主,标题下内容太多再二次固定切。
  • 代码库:按函数、类、方法切,一个函数尽量完整放在一个chunk里。
  • 合同条款:按条款编号切,一条条款最好别拆开。
  • FAQ:按问答对整体保留,问题和答案放在同一个chunk里。

2.2 嵌入:把一句话变成"语义坐标"

切完之后是嵌入。嵌入模型把每个chunk映射成一个高维向量,语义相近的文本在高维空间里的方向接近。用户输入问题后,系统把问题也转成向量,在知识库里找方向最近的几个向量,返回对应的原文。整个过程,你可以理解成"语义版的搜索引擎"。

选型方面,我的建议很直接:

  • 中文文档为主,优先BGE系列(bge-m3)或M3E,中英双语场景也不吃亏。
  • 英文文档,OpenAI的text-embedding-3-small就够用,省心。
  • 本地离线环境,Ollama拉一个bge-m3,CPU也能跑,速度稍慢但可用。

这里有一个我特别想强调的坑:查询文本和文档文本必须使用同一个嵌入模型。有朋友本地索引用bge-m3,调试时图方便把查询切到OpenAI接口,结果向量空间不在一个坐标系里,检索质量直接归零。这不是配置写错了,是典型的"坐标系错乱",排查起来特别迷惑人。

嵌入维度也值得留意。bge-m3是1024维,OpenAI小模型是1536维。本地向量库几万条chunk问题不大,但生产环境数据量上到百万条,就要考虑降维或向量量化,不然存储和检索耗时都会失控。

2.3 检索与排序:召回靠潜力,精排看实力

嵌入模型会给每个chunk打一个相似度分,直接按Top-K取是不是就够了?不够。向量检索擅长语义匹配,但对精确符号、型号、编号这类关键词不敏感。比如用户问"SKU#8847的库存",向量检索容易被"库存"带偏,匹配到一堆讲库存策略的段落;而BM25这种关键词检索,能精确命中"8847"这个编号本身。

所以生产环境里,我更建议用混合检索:向量召回做语义补充,BM25做精确匹配,两个结果集合并后统一排序。这是RAG基础篇里最值得提前养成的习惯之一。哪怕当前数据量不大,先把混合检索架构搭好,后面换数据、换场景都不用推倒重来。

排序阶段,必要时再加一层重排(rerank)。第一轮搜出Top-20或Top-50的候选,用交叉编码器(Cross-Encoder)逐条和问题计算更精细的相关度,挑出Top-3或Top-5送给模型。整个过程可以理解成海选简历和HR终面的区别:向量检索从几万个chunk里筛出候选名单,重排模型再从候选名单里挑最终人选。

基础阶段不用一上来就上重排。先设置k=5,用Chroma的as_retriever拿Top-5,跑通管道后再观察:如果发现Top-5里混着明显不相关的内容,再考虑接reranker。逐步加复杂度,才是稳妥路径。

3. 25分钟跑通:本地最小RAG管道从零搭建实录

3.1 环境选型:别在第一步纠结太久

搭建最小RAG管道,要三个组件:大模型生成器、嵌入模型、向量库。

  • 生成器:本地用Ollama跑qwen2.5:7b,免费,API形式和OpenAI兼容,开发体验很顺;有预算直接用云API,速度和稳定性更好,适合直接上生产。
  • 嵌入模型:本地用Ollama里的bge-m3;也可以调云端embedding接口,效果稳定。
  • 向量库:入门阶段用Chroma,一个pip安装就能跑,自动持久化到本地目录,适合做原型;数据量大了再换Milvus或FAISS。

安装依赖:

pip install langchain langchain-community langchain-chroma chromadb

拉取本地模型:

ollama pull qwen2.5:7b ollama pull bge-m3

向量库选型对比,我整理了一张表:

向量库适合场景特点
Chroma个人项目、原型验证最简单,自动持久化到本地目录
FAISS中等体量检索性能高,但索引文件需要自己管理
Milvus生产大规模分布式部署,支持混合检索和各种过滤条件

我当时选Chroma,就是因为省事。一个目录就把向量库落盘,重启不丢,demo阶段最怕把时间耗在配置上。

3.2 索引构建:让知识先进库

索引构建的完整流程,贴一段可以直接跑的代码:

from langchain_community.document_loaders import DirectoryLoader, TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import Chroma # 1. 加载文档 loader = DirectoryLoader("./docs", glob="**/*.txt", loader_cls=TextLoader) documents = loader.load() # 2. 切分成chunk splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=100) chunks = splitter.split_documents(documents) # 3. 嵌入并入库 embeddings = OllamaEmbeddings(model="bge-m3") vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./kb" )

每步的作用分别是:DirectoryLoader批量加载目录下所有指定类型的文件,glob参数控制文件匹配规则;RecursiveCharacterTextSplitter按递归分隔符分块,是我见过对中文文档最友好的一款通用切分器;OllamaEmbeddings指定本地嵌入模型;Chroma.from_documents负责建库并持久化到./kb目录。

这里提醒一下:加载器要按文件类型选。上面代码只处理txt,如果是PDF,要换成PyPDFLoader;如果是Word,要用Docx2txtLoader。不然PDF里的文字会被读成一堆乱码,后续检索质量惨不忍睹。

一个小经验:索引构建完,先打印几个chunk看看内容是否完整,确认overlap真的生效了。这一步能提前发现90%的切分问题,别等上线了再回头查。

3.3 检索生成:让Agent学会"先查再答"

索引建好后,检索生成就简单了。先拿检索器:

retriever = vectorstore.as_retriever(search_kwargs={"k": 5})

然后手写最简问答流程。我推荐先手写而不是直接用现成的Chain,因为管道每一步都透明可控:

def ask(question): docs = retriever.invoke(question) context = "\n\n".join([d.page_content for d in docs]) prompt = f"""你是一个严谨的助理。请只根据下面的资料回答问题;如果资料里没有答案,直接说"资料中未找到相关信息",不要编造。 资料: {context} 问题:{question}""" response = llm.invoke(prompt) return response

这里最关键的是prompt约束:"只根据资料回答""没有就说不知道"。这两句话是RAG生成环节最重要的护栏。模型是概率生成器,你让它自由发挥,它真的会给你补一大段合情合理的猜测。把约束写死,幻觉至少能砍掉一半。

如果检索到的资料很长,可能超过上下文窗口,这时可以用map_reduce或refine方式,让模型先分段总结再整合。但基础阶段,大多数文档用stuff方式直接塞进prompt就够,简单高效。

还有一个小技巧:在回答里带上引用来源。比如:

for d in docs: print(d.metadata.get("source"))

把来源文件名拼在答案末尾。用户能看到出处,信任度会高很多;运维排查问题时,也能立刻知道答案是哪份文档哪段内容支撑的。这个习惯越早养成越好。

3.4 参数调优:Top-K、切块大小和阈值怎么配合

RAG管道的几个核心参数,我整理成一张速查表:

参数推荐范围作用
chunk_size400-800决定每个片段能承载多少上下文
chunk_overlap50-150防止边界切断语义,造成信息残缺
k(召回条数)3-8决定有多少片段进入生成环节
score_threshold0.3-0.5低于阈值的片段会被丢弃,防止噪声

我实测比较稳的起步组合是:chunk_size=500、overlap=100、k=5、不设score_threshold。为什么这样选?500字对中文业务文档来说,恰好能覆盖一个完整知识点,又不会在一个片段里塞进太多主题;overlap取100,能覆盖常见的句子跨越边界;k=5给模型足够参考,又不至于让prompt太长影响生成速度。

这几个参数是联动关系。chunk_size调大,一个片段里信息变多,k可以适当调小;chunk_size调小,片段变碎,k要适当调大才能凑足上下文。调优一定要带着具体问题做回归测试。我自己一般准备20条代表性Q&A,每改一轮参数就全量跑一遍,对比答案质量,按"正确、部分正确、错误"三档打分。不看体感,看评分,参数调起来才不迷路。

4. RAG实战翻车记录:高频问题与排查速查表

4.1 检索为空或答案答非所问

这是最高频的一类问题,排查顺序很重要。

第一步,打印召回结果。单独调用retriever.invoke(question),看前几条到底相不相关。召回质量差,问题在检索端,别去折腾生成端。

第二步,检查切分是否破坏语义。召回片段总是半截话,先调overlap,再考虑按文档结构重新切。

第三步,检查嵌入模型的语言匹配度。文档是中文,却用了纯英文优化的embedding模型,效果会非常差。中文文档优先bge-m3或M3E。

第四步,启用混合检索。向量检索加BM25,是解决"精确关键词不灵敏"的通用方案:

from langchain.retrievers import EnsembleRetriever from langchain_community.retrievers import BM25Retriever bm25_retriever = BM25Retriever.from_documents(chunks, k=5) vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 5}) ensemble = EnsembleRetriever( retrievers=[bm25_retriever, vector_retriever], weights=[0.4, 0.6] )

权重0.4和0.6是我常用的起步值,语义为主、关键词为辅。如果业务里精确编号特别多,可以把BM25权重调高。

第五步,检查threshold设置。加过score_threshold又召不回内容,很可能是阈值太高,把相关片段误杀了。排查时先去掉阈值看原始召回,再决定阈值取多少。

4.2 答案还是编造,模型不听约束怎么办

生成端幻觉是RAG绕不开的硬仗,通常有三个原因。

一是prompt约束不够。模板里没有"资料中没有就说不知道",模型就会自由发挥。把这句话写死,效果立竿见影。这是我第一条建议,先做这个,成本最低。

二是召回内容相关,但模型推理链条太长。模型在片段之间脑补出了资料里不存在的"连接"。对策是要求它用资料原话支撑结论,减少延伸推理。

三是召回内容本身不完整。用户问题覆盖两个知识点,召回只覆盖一个,模型自然会补一个"合理猜测"。这要靠切分和参数来解决:把chunk切得更细致,或者调大k,让更多片段进入上下文。

还有一个很实用的技巧:把来源引用展示给用户。我在一个客服Agent项目里,把答案末尾拼上来源文件名和页码,用户能看到出处后,无效投诉量明显下降。这不是因为模型变准了,而是因为用户和运维都能快速定位问题——反馈从"你答错了"变成"你引用的第三份文档第2节是旧的",排查效率翻了好几倍。

4.3 响应太慢,怎么优化

RAG管道的耗时大头排序是:LLM生成、embedding/检索、文档加载。优化策略按性价比排序:

  1. 高频问题做缓存。相同问题直接命中之前答案,用一个字典缓存都能解决。这招能省掉大量重复生成耗时。
  2. 向量索引调优。Chroma默认HNSW,可以调ef_search和M参数提高召回速度;数据量上来后,换FAISS或Milvus。
  3. 嵌入结果缓存。相同句子不用反复计算embedding,离线批量算好存一份,线上查询直接查缓存。
  4. 生成阶段开流式输出。本地向量库Top-20召回通常在几十毫秒到一两百毫秒,瓶颈基本都在LLM生成上。流式输出虽然没减少总耗时,但能明显改善等待体感,用户没那么容易焦躁。

4.4 知识库更新:改错一个字,全量重建一次?

RAG落地中最容易被忽视的,是知识库更新。文档是活的,产品手册会改版、政策会更新、商品信息会变动。索引不跟着动,Agent就会拿着旧知识回答新问题。

入门阶段最简单直接的做法:删掉旧集合,重建。代码就一行:

vectorstore.delete_collection() # 然后重新执行索引构建流程

几千条chunk的场景,全量重建也就几十秒,完全能接受。重点是别停在一个"能跑"的状态就撒手不管,更新机制要跟上。

生产环境我强烈建议按版本管理collection。知识库v1、v2分开建,切换查询时改collection名。这样能平滑升级,还能在知识库出问题时秒级回滚。增量索引的进阶做法,是给每个chunk维护稳定ID,按文件hash判断内容是否变化,只切分变动的文件,避免全量重建白白消耗算力。

我自己踩过一个特别典型的坑:同一份文档放在两个目录里,索引建了两遍。检索时有时命中旧版本,有时命中新版本,回答前后矛盾,用户被搞到崩溃。所以索引前先做文档去重,按文件名加hash管理,别嫌这一步麻烦。

另外说个真实案例:有朋友做"本地ERP加RAG加LLM的产品检索",一开始把商品描述全塞进文档。后来发现,真正要查的库存、价格这类结构化数据,并不适合做成文本片段。正确做法是让Agent把用户问题转成结构化查询,直接检索数据库,把返回的记录作为上下文。这其实是RAG在生产系统里更高级的形态,等大家把基础管道跑熟后,值得单独深入研究。

4.5 进阶方向:Agentic RAG和GraphRAG,但先把基础打牢

聊完基础,我多说一句进阶方向,因为很多同学搭完基础管道就问"接下来学什么"。

Agentic RAG是让Agent自己决定检索策略:先检索一次,发现不够,就改写查询再检索,甚至决定是查知识库还是调工具。GraphRAG则是先把文档里的实体和关系抽出来构建知识图谱,更适合多跳推理的问题。这两个方向都很有意思,也是RAG热度一直不降的原因。但有一条线我劝你守住:进阶的前提,是把基础管道做可观测。查询日志、召回片段预览、来源引用、版本回滚,这三样没做好之前,别急着上Agentic RAG。否则Agent行为越复杂,错误越难定位,最后系统会变成一个无法维护的黑盒。

最后说点个人感受。我自己做RAG项目,最深的体会是:RAG的成败根本不取决于模型选多强,而在于那些看起来不起眼的工程细节——切分粒度有没有管好、来源引用有没有加上、知识库版本能不能回滚。前前后后推倒重来的两次项目,都不是模型不行,而是知识库管理出了问题:一次是重复索引导致新旧版本混用,另一次是没有任何引用手段,用户质疑答案时完全无从查起。RAG作为Agent的知识获取管道,技术点不算多,但它是一门工程。把基础管道做扎实,把日志和引用做足,这个系统才算真正从demo走到了生产。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 15:35:11

大模型备案与算法备案区别详解:API场景下的实操指南

1. 先把两个备案的边界划清楚大模型备案和互联网算法备案,这两个词在过去一年里被问到的频率高得离谱。很多做AI应用的团队在准备材料时才发现,自己以为只需要做一个备案,结果被要求补另一个;也有团队把两份材料混在一起提交&…

作者头像 李华
网站建设 2026/9/28 15:34:46

统一网关 tsm-hub:收编 LLM、Tools、MCP 与 Skills 的实践

做 AI 应用开发这一年多,我最大的感受不是模型不够强,而是“接入的姿势”越来越乱。LLM 要接闭源、要接开源、要接本地部署,调用方式五花八门;Tools 散落在各个服务里,Agent 想用还得自己拼 HTTP,鉴权和超时…

作者头像 李华
网站建设 2026/9/28 15:34:46

CCS导入DSP2833x工程报错#1965?路径配置与头文件排查指南

干DSP开发的人,十有八九都经历过这样一个瞬间:好不容易从同事、导师或者某个技术群里拿到一个CCS工程,满怀期待地导入,点下编译按钮,结果屏幕上冒出一大片红字,fatal error #1965 cannot open source file …

作者头像 李华
网站建设 2026/9/28 15:34:12

四面体笼如何显著降低BVH内存占用:原理、实现与优化

1. 从内存瓶颈说起:为什么BVH的存储问题值得死磕做图形学和实时渲染的人,迟早会撞上BVH这堵墙。BVH,Bounding Volume Hierarchy,层次包围盒,是光线追踪、碰撞检测、视锥剔除这些场景里绕不开的空间加速结构。它的核心思…

作者头像 李华
网站建设 2026/9/28 15:33:10

Rokid AIUI实现语音+头控双模推箱子

1. 项目概述:当语音交互撞上经典解谜,一个“不用手”的推箱子诞生了我最近用Rokid的AIUI平台搭了个特别有意思的玩意儿——童年回忆杀《推箱子》的语音头控双模版本。不是简单把游戏搬进AR眼镜里,而是彻底重构了交互逻辑:你不用碰…

作者头像 李华
网站建设 2026/9/28 15:33:05

AI编码代理上下文工程:从滑动窗口到MCP的实践

1. 上下文为什么先爆掉,而不是模型能力先不够前阵子我把一个自用的AI编码代理丢进一个中型仓库里去改一个跨模块bug,刚开局一切正常,它还能准确定位文件;但跑了二十多分钟之后,画风开始失控——它反复调一个已经被删除…

作者头像 李华