1. RAG是什么,以及为什么它突然成了必选项
RAG(Retrieval-Augmented Generation,知识检索增强)这几年在AI应用圈的出镜率,已经高到快赶上大模型本身了。但很多人对它的理解还停留在“给大模型外挂一个知识文档”这个模糊印象上,真要上手落地,踩的坑一个接一个。我做了几个RAG项目之后,最大的感受是:这玩意儿不是“把文档塞进向量数据库再拼进Prompt”那么简单,它是一个需要从检索策略、内容切块、生成约束到召回质量评估综合考虑的系统工程。
先说清楚它到底解决什么问题。大模型的知识是训练时固化的,这意味着两件事:一是它不知道你公司内部最新的业务规则、私有数据;二是它的知识存在截止日期,超过那个时间点的事它一概不知。RAG的思路很直接,别指望大模型“记住”所有事,而是在每次回答前,先从你准备的知识库里检索出相关片段,把这段内容作为背景材料塞进Prompt,再让模型基于这些材料生成答案。整个过程类似于开卷考试:模型不需要背诵全部知识点,只需要学会怎么查资料并用资料答题。这个“开卷”设计带来的好处是实实在在的:知识可随时更新,不用重训模型;回答可以被溯源,引用索引能指向原文;幻觉率显著下降,因为内容有了事实锚点。
什么人需要关注RAG?三类人最刚需。第一类是做企业知识库和内部问答系统的,比如客服辅助、员工手册查询、制度检索;第二类是做垂直行业应用的,比如法律条文问答、医疗指南问答、设备维修手册问答,这类场景对准确率要求极高;第三类是做大模型应用层开发的,不管你是做垂直模型还是做Agent,想把外部数据接进来,RAG都是最主流的技术路径。这篇文章我不讲泛泛的概念,而是从系统设计的视角,把RAG拆成检索端、生成端、知识库选择、本地实战、常见坑位这几个层面,一步步讲透,保证你能照着搭建一套能用的系统。
2. 整体架构拆解:为什么RAG不能只盯着生成环节
2.1 RAG的四个核心模块
一个完整的RAG系统,至少包含四个模块:知识库构建、检索召回、上下文组装、生成增强。很多人做RAG只关注“生成增强”这一步,觉得Prompt写好了,效果就有了,结果上线之后发现回答质量很差,问题根源往往出在前面的知识库和检索环节。
知识库构建模块负责把原始文档拆分成适合检索的单元,再转换成向量或结构化形式存储。这里面的难点是切块策略:切大了,检索命中一个块可能包含大量无关内容,拉低生成质量;切小了,语义可能被切碎,召回时找不到完整答案。检索召回模块负责用Query去匹配知识片段,通常有两条路:向量检索做语义召回,关键词检索做精确匹配,实践中往往是两者结合。上下文组装模块则是把召回结果做重排序,筛选出最相关的TopN片段,再格式化拼接成Prompt。很多人忽略这个模块,直接把所有召回片段都塞进去,结果token爆掉、有效信息被淹没。生成增强模块就是调用LLM生成最终答案,这个模块能做的文章不多,核心是把Prompt写好,把约束条件列清楚。
2.2 为什么先检索后生成,这个顺序不能改
有一种常见误区是把RAG理解为“让模型联网搜索”或“让模型读取文件”,在技术实现上完全是另一回事。RAG的顺序是严格的:先针对用户的问题去知识库做查询,拿到候选内容;再把候选内容和原始问题拼在一起,交给生成模型;最后生成模型根据上下文输出回答。这个顺序之所以不能改,是因为生成模型的注意力机制是单向的,它只能基于输入的上下文作答。如果你先把问题交给模型,再让它“回想”相关知识,模型只能依赖参数记忆,这等于退化成了普通问答,知识库的作用就没有了。
有个类比很贴切:RAG像是给员工配了一个档案管理员。员工自己记不住所有档案内容,但每次需要信息时,管理员先把相关档案翻出来放在桌上,员工再阅读档案、结合自己经验写出答案。这个档案管理员的工作质量直接决定答案质量——如果管理员翻错了档案,员工只能写出错的答案。这就是为什么检索端在RAG里有决定性地位,我在项目中感受极深,一开始把精力全放在Prompt调优上,效果始终提不上去,后来沉下心优化切块和召回,回答质量立刻上了一个台阶。
2.3 RAG应用场景的三层划分
不同场景对RAG的依赖程度和实现重点完全不同,我习惯把它分成三层。
第一层是“资料问答型”,典型场景是客服知识库、产品FAQ、政策查询。这一层用户问的是事实性问题,答案在文档里能找到原文,RAG要解决的核心问题是“查得准”。评价指标主要是召回命中率和答案忠实度,实现时重点优化切块策略和检索排序。
第二层是“分析推理型”,典型场景是行业研报分析、科研文献综述、法律案例研判。用户问的是需要跨文档整合信息的问题,答案不在某一句话里,而是散落在多个段落中,需要模型做推理和整合。这一层RAG要解决的核心问题是“信息拼图”,实现时通常要做多路召回、重排序、多轮检索,甚至要让模型自己判断哪些信息缺失。
第三层是“操作决策型”,典型场景是运维故障排查、医疗诊断辅助、设备维修指导。这一层RAG不只是回答问题,还要输出步骤、方案、决策依据。这时候知识库里的内容往往是以经验、规范、案例形式存在的,RAG要解决的核心问题是“方案的完整性”,实现上经常结合知识图谱、决策树等结构化知识来增强。我在后面会展开讲知识图谱和RAG的结合,这是最近被讨论最多的方向之一。
3. 检索端的核心细节:“查得到”比“生成得好”更重要
3.1 切块策略:到底该切多大
切块是整个RAG系统里最容易被低估的一环。块太小,比如100字符,语义单元过于碎片化,一个问题通常需要拼凑多个片段才能组成完整答案,命中率低;块太大,比如2000字符以上,单个块里包含大量噪声,向量化时相互稀释,检索精度降低,而且塞进Prompt会浪费大量token。实际项目中我推荐一个基础黄金区间:512到1024字符左右,但这只是起点,真正靠谱的切块策略必须跟着文档类型走。
结构化的文档(有章节、小节、标题),应该先按标题层级切成语义块,再在每个块内局部切分,始终保持“块内有完整逻辑”。非结构化的连续文本(比如论文、说明书),用滑动窗口配合重叠切块,相邻块之间保留50到100字符的重叠,避免把关键证据恰好切碎在边界上。代码类的文档必须按函数、类来切,不能单纯按字符切,否则语义完全散掉。这里有一个很细节的坑:表格千万不要切成碎片。一个包含50行数据的表格,如果被切成了多个文本块,向量检索几乎不可能把它召回成完整的表,我后来的方案是把整个表格按Markdown格式作为一个特殊块存储,并在前面加一句描述性文本,检索命中率立刻提升。
3.2 向量检索和关键词检索的配合
向量检索的优点是能处理“语义相近但关键词完全不同”的查询,比如用户问“怎么退换货”,文档里写的是“退款退货流程”,向量化后两者都能映射到相近的语义空间。但向量检索也有明显软肋:专有名词、型号、编号、人名这类高频词,向量化时经常被稀释掉,用户输入“故障代码E210”时,你期望的是精确匹配到文档里包含“E210”的段落,但向量检索可能给出的是“设备发生异常”这类语义相近但毫无用处的片段。
所以我的实践结论是:必须做混合检索。简单方案是向量检索和BM25关键词检索各跑一路,然后做结果合并去重再重排;进阶方案是整一个RAG框架,现在主流框架比如LangChain、LlamaIndex都内置了这种混合检索的能力,还有个叫“RRF”(Reciprocal Rank Fusion)的经典合并算法,逻辑不复杂:对不同检索结果分别打分排秩,最终得分等于各列表秩次倒数的累加,实现简单、效果稳定。我实测下来,混合检索相比纯向量检索的召回率提升大概在10到20个百分点,具体取决于文档领域,但在含大量专有名词的文档上提升尤其明显。
3.3 重排序(Rerank)为什么是效果倍增器
召回阶段为了保召回率,通常会多取一些候选片段,比如Top20。但候选多不代表答案就好,中间混杂着大量无关片段,如果不做筛选直接丢给生成模型,模型容易被噪声带偏。重排序就是用一个更精细的模型对候选片段重新打分排序,通常是用Cross-Encoder结构,把Query和每个候选片段拼在一起输入模型,输出相关性分数。这个步骤效果好但代价不低,实际项目中我建议分两级:第一级用粗排(向量和关键词的快速合并)从全库筛出Top50,第二级用Rerank模型从Top50选出最终Top3到Top5。注意Rerank的候选数量不要贪多,因为Cross-Encoder的计算复杂度是候选量线性增加的,一次20个片段就能明显感知延迟,线上服务建议控制在10个以内。
有朋友问能不能跳过错这一步,我的回答是:如果你的知识库只有几百个块,生成效果或许还能凑合,但一旦规模上千上万,Rerank就是刚需。没有Rerank,准确率大概率在60%-70%徘徊,加了之后能稳定到85%以上,这个差距在业务侧是无法接受的。
4. 知识库的类型选择:从向量库到知识图谱,别走错路
4.1 向量知识库和结构知识库的本质差异
热搜词里反复出现“rag知识库和结构知识库区分”这个搜索,我猜很多人其实是被“知识库”这个笼统的词给搞混了。向量知识库是目前RAG的主流形态,它存储的是“文本块”的向量表示,底层通常是向量数据库,比如Milvus、Qdrant、Weaviate,或者传统数据库的向量插件。它的特点是构建容易,把文档切块、向量化、灌入数据库就行,检索方式是“找相似片段”,适合处理非结构化文本。但它的局限也很明显:它不理解概念之间的关系,不懂“A公司是B公司的母公司”这种结构语义,也无法处理多跳推理。
结构知识库(知识图谱)存储的是“实体—关系—实体”的三元组,比如(北京,是首都,中国)、(iPhone15,所属品牌,Apple)。知识图谱用图结构显式表达实体之间的关系,天然支持复杂关联查询和多跳推理。比如用户问“哪些供应商同时给A公司和B公司供货”,知识图谱可以做图遍历找到交集实体,这在向量库里几乎无法实现。
有人问那个经典问题:我要给知识库存图片,RAG能做吗?这里面有个理解误区,先拆开讲:RAG本身可以引用图片,最近多模态大模型比如GPT-4V已经可以直接把图片喂给模型做理解,但你存进去的图片必须能被“检索到”才有意义。向量知识库能存图片的向量表示,做法是用CLIP这类多模态模型把图片编码成向量,检索时用文本Query去匹配图像向量,所以“用文字搜图片”是可以做到的。但如果你的图片不是被检索的目标,而是包含在某个文档段落里的配图,想让它随着文字一起被召回并参与问答,那就要看你用的生成模型支不支持多模态输入了。我在一个售后知识库项目里试过把故障截图存进RAG,发现文本检索到对应段落毫无问题,但图片信息必须靠多模态模型才能读出来,普通文本模型看到的就是一张图的占位符。所以在选型之初就要想清楚:你要存图片是要做“以文搜图”,还是要让模型看图说话。前者纯向量库就能做,后者必须在模型层配多模态能力。
4.2 知识图谱与RAG的结合(GraphRAG / Ontology RAG)
最近“ontology rag”这个热词出现在大家视野里,本质上讲的是把知识图谱(包括轻量级的本体、模式层)和RAG结合起来。
纯向量RAG有个著名的“瓶颈”:答案需要跨多个文档的多个片段拼接时,向量检索经常只能捞到其中一部分,像个炸鸡拼盘缺了主菜,结果模型只能靠上下文中去猜其他部分,幻觉风险急剧上升。“图检索增强生成”(GraphRAG)的解决思路是把事实以三元组存入图谱,推理时先从问题中抽取实体,再沿着图的边展开多跳检索,把子图作为结构化上下文交给模型。这个方案的多跳推理能力极强,适合复杂知识密集型场景。
但图谱RAG有个现实门槛:构建和维护成本高,把无结构文档自动抽取成高质量三元组,准确率很难做到95%以上,需要不少人工校对。所以我的建议是采纳一个折中方案:常规RAG以外,只把高价值实体关系(比如设备型号与配件兼容性、组织层级与人员权限、法条之间的引用关系)做成一个小型的领域知识图谱,检索时先用问题匹配图谱实体得到结构化线索,再用线索去向量库做二次召回。这就是“结构化线索增强向量检索”,实测下来两路结果合并之后,多跳问题的回答准确率至少提升20%。
4.3 本体(Ontology)的作用:从“搜到什么”到“缺什么就知道补什么”
再往深一层次说,ontology之所以在RAG里被频繁讨论,是因为它能给检索一个“骨架”。本体定义了领域里有哪些概念、每种概念的属性以及概念间的关系。比如一个医疗知识库,本体里定义了“疾病—症状—药物—禁忌症”的关系模式。有了这层模式,系统在回答“某种药物能不能给肾功能不全的患者用”时,就不是单纯找相似文本了,而是按“药物-禁忌症-人群”这个路径去图谱里检索。这种按路径检索的方式,准确率远远高于无差别的向量匹配。
我做一个设备维修知识库时体验很深。纯向量RAG模式下,用户问“轴承温度过高可能是什么原因”,检索经常既跳到“轴承装配流程”,又跳到“温度传感器校准”,相关性都高但方向不对。后来引入了一个简单的维修本体,定义“故障现象—可能原因—排查步骤—解决方案”的关系链,再按这个结构检索,返回的内容就精确匹配到故障排查类的文本上了。这就是本体(Ontology)的威力:它不直接提升模型的理解能力,但极大改进了知识库的组织方式和检索路径。
4.4 向量知识库的选型建议
如果你最终决定先用向量知识库起步(现阶段大部分项目都是这么做的),选型上我给你一些实测心得。
- 数据量在百万级以内,优先考虑用Qdrant或Chroma,部署简单,社区资料多,适合快速验证。
- 数据量达到千万级,Milvus是主流选择,分布式能力更强,但运维复杂度也上来了,要有心理准备。
- 不想引入额外组件,想用现有PostgreSQL的,pgvector够用,但性能上限低于专用向量库。
- Elasticsearch如果已经在用,它的KNN搜索也能顶上,好处是文档检索和向量检索一套体系打通。
向量数据库的度量方式(Metric)要注意,常见的有余弦相似度(Cosine)、欧氏距离(L2)、内积(Dot Product)。文本向量普遍选择余弦相似度,因为它只关注方向性,对向量模长不敏感,对文本长度差异较大的场景更稳。图省事的话,很多向量库会默认用余弦,但你接的是OpenAI的Embedding模型时,官方建议用点积并做长度归一化,效果一样,但性能和数值稳定性更好。这个细节踩过坑的人少,但影响不小。
5. 从零搭建:在Mac上跑通一套RAG实战项目
5.1 本地部署的软硬件准备
“怎么在mac上搭建rag知识库”能上搜索热词,说明Mac用户群体确实有这个需求。Mac上跑RAG有一个天然优势和一个天然限制。优势是Apple Silicon(M1/M2/M3系列)的芯片统一内存架构允许把模型直接加载到GPU显存(实际是共享内存),跑7B、13B量级的开源模型速度可用;限制是显存(统一内存)终究有限,个人电脑跑不动超大模型,且依赖OpenAI这类云端API时的网络、成本也是变量。
我推荐的Mac本地方案是三步走:本地装Ollama作为LLM推理引擎,本地或远程接一个向量库(轻量级用Chroma即可),中间用LangChain或LlamaIndex做编排。这样一套下来,不开任何云端API,完全离线也能跑通一个RAG问答系统。如果追求速度和稳定,我建议Mac本地直接部署Ollama跑推理,拿到API地址后,检索和编排可以全部放在代码里执行。
5.2 环境安装与依赖配置
我用的是Python 3.10,先把依赖装好:
# 创建虚拟环境 python3 -m venv rag_env source rag_env/bin/activate # 安装核心依赖 pip install langchain langchain-community chromadb ollama然后安装并启动Ollama:
# 安装Ollama(macOS推荐Homebrew方式) brew install ollama # 启动服务 ollama serve # 下载一个质量性价比都不错的选手:qwen2.5:7b ollama pull qwen2.5:7b嵌入模型(Embedding)我推荐用本地的nomic-embed-text,在Mac统一内存上跑得很轻,效果中规中矩,关键是纯本地不花钱。
ollama pull nomic-embed-text5.3 核心代码:文档加载、切块、入库、检索
下面这段代码是个完整的Demo流程,从加载一份Markdown文档到完成一次RAG问答,可以直接复制运行。注意这里演示的是最小闭环,生产环境你需要加上异常处理、重试、日志等工程能力。
from langchain_community.document_loaders import TextLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import Chroma from langchain_community.llms import Ollama from langchain.chains import RetrievalQA # 1. 加载文档 loader = TextLoader("knowledge_base.md", encoding="utf-8") documents = loader.load() # 2. 切块:这里用递归字符切分,带重叠 splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=80, separators=["\n\n", "\n", "。", "!", "?", " ", ""] ) chunks = splitter.split_documents(documents) # 3. 向量化 + 存入Chroma embeddings = OllamaEmbeddings(model="nomic-embed-text") vectorstore = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory="./chroma_db" ) # 4. 构建本地LLM llm = Ollama(model="qwen2.5:7b", temperature=0.2) # 5. 创建检索问答链 qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=vectorstore.as_retriever(search_kwargs={"k": 4}), return_source_documents=True ) # 6. 执行问答 response = qa_chain.invoke({"query": "你的知识库里,关于退换货的流程是什么?"}) print(response["result"])这段代码里最需要打磨的是第2步的切块参数和第5步的TopK值。chunk_size我设了500,是综合考虑了问答效果和token成本之后的选择,如果文档里表格多、长段落密集,建议把chunk_size调到800并将chunk_overlap提到100。k值设4意味着取四个片段拼进Prompt,但如果问题复杂,四个片段常常不够,我建议代码里先固定k=4跑通,后续优化时再把检索结果打印出来看看召回质量,再决定往上调到6还是8。
5.4 混合检索的快速实现
前面说了混合检索的价值,这一步直接用LangChain自带的方法实现。代码逻辑很简单:先分别用向量检索器生成相关文档的索引,再做一个关键词检索器(这里接BM25),然后用LangChain里的EnsembleRetriever把它们合并起来,权重按需分配。
from langchain.retrievers import BM25Retriever, EnsembleRetriever bm25 = BM25Retriever.from_documents(chunks) bm25.k = 4 vector_retriever = vectorstore.as_retriever(search_kwargs={"k": 4}) ensemble = EnsembleRetriever( retrievers=[bm25, vector_retriever], weights=[0.4, 0.6] ) qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=ensemble, return_source_documents=True )这里权重分配是有讲究的:我给BM25设了0.4,向量检索设了0.6,原因是大多数文档场景下,语义相关性比关键词匹配更重要,但关键词能兜住专有名词的精确匹配。如果文档里型号、编号非常多,可以把BM25权重提高到0.5甚至0.6,自己按数据实测调。
6. 实战中的常见问题与排查技巧
6.1 依赖注入率低,答案总在“答非所问”
这是RAG项目里最普遍的瓶颈,没有之一。具体表现是:你问它一个很具体的问题,它给的答案要么宏观到废话,要么干脆是错误信息。排查路径我整理成了口诀:先看召回,再看Prompt,最后调模型。
第一步,先看召回命中没有。我习惯把每一步检索到的候选片段直接打印出来,肉眼比对“命中的块”是不是真的包含了能回答问题的事实。如果候选片段里根本没有答案信息,问题就出在知识库构建或检索策略上,这时候调Prompt一点用都没有。如果命中片段里有答案,模型却没答对,那才是Prompt或模型的问题。
第二步,检查Query本身。RAG系统常犯一个错误:用原始用户问题去检索。用户的问题往往口语化、信息密度低,比如“我家那个设备老是响怎么回事”,检索时关键词提取效果极差。我常用一个技巧:先用LLM把用户问题改写成一个更适合检索的Query,提取出关键实体和要求,比如上文例子改写为“设备持续发出异常响声,原因排查”,检索命中率立即提升。这叫“查询改写”,属于RAG中成本极低但收益显著的一个技巧。
第三步,查看Prompt模板。别让生成模型自由发挥。我的Prompt模板里固定包含几个部分:系统角色设定,明确要求只依据给定资料作答;明确禁止编造,如果资料里没有答案,直接说“资料中未提及”;引用要求,每条答案必须标注来源编号。这套约束下来,幻觉率能降低一半以上。模板示例如下:
prompt_template = """ 你是知识库问答助手,请严格依据以下资料回答问题。 资料: {context} 问题:{question} 要求: 1. 如果资料中有明确答案,请直接回答,并注明依据的资料来源编号。 2. 如果资料中没有明确答案,请回复“根据现有资料无法回答”。 3. 严禁编造资料中不存在的内容。 4. 回答控制在200字以内,先给结论,再给依据。 你的回答: """6.2 检索顺序错乱,拼出逻辑不通的答案
这是个很有意思的坑。多个召回片段本身都对,但拼在一起时顺序错了,生成出来的答案逻辑混乱。比如知识库里有一段描述了“第一步开箱检查”,另一段描述“第五步通电测试”,模型如果先看到第五步再看到第一步,它给出的回答就是倒叙的。这个问题的根源不在模型,而在上下文拼装顺序缺乏约束。
我的解决套路是两招并用。一是在切块时尽量保持“步骤类内容在同一个块里”,比如把整个操作流程按一级标题切到一个大块中,或者要求文档作者把步骤编号写在每步开头,然后用正则按编号提取。二是在Prompt里加一句“请根据步骤编号或时间顺序组织答案”,这能抵消一部分乱序影响。但如果知识库本身混乱,别指望Prompt能拯救一切,还是得回头优化知识和切块。
6.3 Mac本地跑RAG时常见的性能与兼容性问题
Mac上跑这套方案,我经历过几个高频Bug,列出来帮大家避坑:
- Ollama首次推理特别慢。这不是故障,是模型在加载到统一内存,第一次推理需要做权重加载和预热,后面就快了。我实测Qwen2.5 7B在M2 Pro上首次推理大约40-60秒,后续单轮问答大概5-10秒,可以接受。
- 向量库持久化目录冲突。Chroma的
persist_directory如果多次指向同一目录,且代码改动导致schema不匹配,会报错。排查方法是删除目录重新构建,或者用Chroma(persist_directory=..., collection_name="...")区分不同集合。 - Embedding模型和LLM模型加载在同一个Ollama服务里,显存冲突。Mac的统一内存虽然足够大,但跑7B模型已经把大部分带宽吃掉了,再加载Embedding模型,会出现推理速度明显下降甚至OOM。我的解决方案是:Embedding用一个独立的轻量模型实例,跑在CPU上或者单独开一个Ollama服务端口,两个模型解耦。
- 中文分句用英文标点。
RecursiveCharacterTextSplitter默认分隔符是按英文习惯设计的,如果文档是中文长段落,必然出现切块不准的问题。务必像我前面代码那样自定义separators,把“。!?”加进去。这一步盯着容易忽视,但影响极大。
6.4 提高效果的三板斧
当你把基础链路跑通后,想进一步提升生成效果,我总结了三板斧,按性价比排序:
- 查询改写和HyDE。查询改写前面说过了,HyDE(假设性文档嵌入)是进阶版,先用LLM针对问题写一个假设性答案,再用这个假设答案去检索。因为假设答案和知识库里的真实文本在语义上更接近,能提升向量检索的命中率。代价是多一次LLM调用,延迟增加,适合离线批处理场景。
- 重排序。前面在3.3节详细讲过了,这里再强调一下:这是效果提升最明显的单点操作。
- 多轮对话中的检索上下文管理。用户可能会追问“那价格呢?”,这时如果只拿这句去检索,召回结果完全不可用。正确的做法是结合对话历史,把“那价格呢”改写为“这个产品当前价格是多少”,再去做检索。这个上下文改写逻辑需要你在应用层自己管理对话历史,LangChain里可以用
create_history_aware_retriever来做,建议直接上手。
7. 从RAG到Agent:知识检索增强的下一步扩展
RAG并不是终局。我把RAG叫“知识检索增强1.0”,它的局限是:检索一次、生成一次,模型没有反思和计划的机会。比如用户问一个多跳推理问题,RAG可能第一次检索漏了关键信息,模型没有发现问题,直接给出不完整答案。
更高级的做法是把RAG做成一个Agent化流程:模型不再只是“检索+生成”,而是拥有“计划、检索、观察、再检索、生成”的循环能力。比如用一个“ReAct”式Agent,模型先决定检索什么,拿到结果后判断信息够不够,不够就再检索一次,够了才生成最终答案。这个模式确实能显著提升复杂问题的回答质量,但对系统的工程要求也高:要有工具调用的能力,要有状态管理,还要控制循环次数防止死循环。
另外一个重要扩展方向是“增量知识更新”。RAG的知识库不会自动学习新内容,你需要建立文档更新、切片重建、索引替换的管线。实战里我踩过一个特别厚重的坑:某次更新了知识库,但持久化的向量库里旧数据还在,新数据也进来了,检索时新旧版本混合,回答的引用部门信息对不上。后来我用带时间戳的collection,每次更新就新建一个集合替换旧的,并且自动清理无用集合。这个思路适用性很强,推荐直接抄。
还有评测体系也要尽早建立。我给你一个检验RAG系统好不好的朴素方法:准备50到100个有标准答案的问答对(覆盖常见问题、边界问题、知识库没有答案的问题),每次改动配置后都跑一遍,计算“答案正确率”和“拒绝率(应该拒绝回答但没拒绝的比例)”。这个评测集的价值被严重低估了,很多人凭感觉调参,效果不稳定就是因为没有量化反馈。
我自己在迭代RAG系统的过程中,最深的体会是:RAG的技术栈其实不难搭,难的是理解“哪些环节决定效果”。切块、召回、重排序、提示词约束,每一步都不复杂,但叠加在一起却能产生巨大差异。而且这中间没有银弹,一切都需要基于你自己的知识库内容和用户问题类型做针对性调整。希望这篇文章能帮你少走那些我用头发换来的弯路,尤其是切块策略和混合检索,建议拿到自己的数据上直接实测一轮,效果的好坏会让所有抽象讨论变得异常清晰。