ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

RAG与大模型微调怎么选?拆解检索增强生成流程与实战

RAG与大模型微调怎么选?拆解检索增强生成流程与实战

最近被问得最多的问题,其实是这么几个:RAG 是不是就是给大模型配了个数据库?大家都在做 RAG 知识库,为什么不直接做大模型微调?RAG 和大模型到底谁依赖谁?

作为一个把本地知识库、RAG 流水线、微调方案都实际跑过的人,我觉得有必要把这件事彻底掰扯清楚。RAG 不是一个神秘的新模型,也不是大模型的替代品,它更像是在大模型外面接了一套"资料检索系统",让模型在回答问题时先查资料、再写答案。本文不堆概念,尽量用大白话把 RAG 和大模型的分工、完整流程、选型逻辑、实战代码和排查经验讲透,适合刚接触大模型应用开发、想在企业内部搭建知识库,或者被各种技术名词绕晕的同学。

1. 先定位:RAG 和大模型到底谁给谁"打工"

1.1 大模型负责"想",RAG 负责"找"

先给结论:RAG(Retrieval-Augmented Generation)的完整叫法是"检索增强生成",它不是一个独立的模型,而是一套围绕大模型设计的外部机制。大模型本身负责"理解和生成",它在你提问时,基于自己的参数知识去组织语言、推理逻辑;RAG 的职责则是在模型开口之前,先去外部资料库把相关的内容"找出来",再把这些内容拼进提示词里,引导模型基于这些材料回答。

用人话说,大模型就像一个文笔很好、但记忆范围有限的员工。如果你直接问他公司年假制度,他可能只能根据训练时见过的通用职场知识来回答,甚至编一个看似合理的答案。RAG 相当于给他配了一个内网资料柜,回答前先去柜子里翻出《员工手册》里"年假"相关的那几页,再让他照着这几页写回复。这样答案有出处、更新及时、还能追踪溯源。

这也是为什么各大企业做私有知识库时往往首选 RAG 而不是直接微调:RAG 不改变大模型本身的任何参数,只是动态地给模型"喂"外部资料。大模型负责的是"想清楚怎么表达",RAG 负责的是"想清楚该找什么材料"。两者各管一段,配合起来才形成一套完整的问答系统。

1.2 知识库里的"知识"是如何被大模型理解的

很多人对大模型有误解,以为它能"直接看懂"PDF、Word、Excel 里的内容。实际上,大模型处理的是 token 序列,也就是文本片段。你给它一个 PDF 文件,它并不能像人一样翻页阅读;它只能接收有限长度的文本输入。知识库里的文档,必须先经过切分、向量化、检索,最终变成一段段普通文本,塞进 Prompt 里,大模型才"理解"到了这些内容。

这里很多同学问:"RAG 知识库能存储图片吗?"先说结论:传统的文本型 RAG 知识库,存的是文档切块后的文本和对应的向量,直接检索图片本身是不行的。但图片可以通过 OCR 提取文字、或用多模态模型生成文字描述,把这些文字内容纳入知识库,再在回答时把相关图片路径一并提供给用户。本质上是把图片"翻译"成了文字后再交给大模型处理,而不是让大模型直接"看"图片。后面第 5 章我会专门展开。

理解这个原理特别重要:既然大模型只能理解文本,那么 RAG 系统的关键就不只是模型强不强,更在于"你能不能把需要的信息准确变成文本,并且在提问时准确捞出来"。

2. 拆开 RAG 这条链路:离线索引与在线推理

2.1 离线索引:文档到向量的"翻译过程"

RAG 的第一阶段是建立索引,也就是把原始文档变成可以被搜索的知识。这个阶段通常在正式问答之前就做完,所以叫"离线"。步骤看起来简单,但里面全是细节:

第一步是加载文档。PDF、Word、Markdown 各有各自的解析方式,PDF 还要考虑扫描件需要 OCR。第二步是清理,去掉页眉页脚、多余换行、无关水印。第三步是最关键的切分(Chunking),把长文档切成若干片段。第四步是调用嵌入模型(Embedding Model)把每个片段编码成一个向量,最后写入向量数据库。

切分为什么这么重要?大模型的上下文窗口是有限的,就算现在很多模型能处理 128K、200K 个 token,也不能把整本手册都塞进去。更重要的是,一段文本越长,和问题的相关性就越容易被稀释。想象你在一本书里查"年假",如果整本书只有一个向量,那么"年假"这个内容会被整本书的其他信息掩盖,检索分数不会突出;如果把文档切成每段几百字的块,每一块都有独立的向量,检索时"年假"相关的块才能精准浮出水面。

但切分也不是越小越好。切得太碎,一个完整的概念被切成两半,语义不完整,召回的片段读起来前言不搭后语。切得太粗,一个块里塞了多个主题,检索出这一块可能只与提问沾一点边。我常用的经验是在"语义完整"和"块大小"之间找平衡:从 256 到 512 个 token 之间尝试,再用滑动窗口让相邻块有少量重叠,避免一段内容恰好被从中间截断。

由于文本变成了向量,向量数据库里实际存的是两类东西:向量本身,以及对应的原始文本片段。向量用来做相似度计算,原始文本用来拼进 Prompt 和展示给用户看。

2.2 在线推理:从问题到答案的四个阶段

当检索库已经建立好,来了一条新问题时,系统会走一个典型流程:

第一阶段是查询处理。你可以直接把用户问题拿去检索,也可以先让大模型把问题改写得更适合检索。用户问"大家年假一般能休息多久?",改写模型可能把它转换成"公司年假制度与休假天数",这样与知识库中《员工手册》的表述更匹配。

第二阶段是向量召回。把改写后的问题交给同一个嵌入模型,得到问题的向量,然后在向量库里找出与它最相似的 Top K 个片段。这里的相似度一般用余弦相似度来算,取值范围在 -1 到 1 之间,越接近 1 代表方向越一致。在实际工程里,TopK 的选择很关键:K 太小可能漏掉关键材料,K 太大又会把一堆低质量内容塞进上下文。我的项目一般先设 K=5,再根据效果调整。

第三阶段是精排(Rerank)。向量召回看的是语义相似,有时候召回来 5 段,有 3 段都沾边但排在前面的未必是最准的。所以更稳的流水线会加一个 Rerank 模型,把召回的候选片段按和问题的匹配度重新打一次分,挑出真正有用的两三个片段。这一步不是必须的,但加了之后效果提升非常明显。

第四阶段是生成。把最终选中的片段合并成一段文本,连同用户问题一起拼进 Prompt,发给大模型,让它在限定的上下文里生成答案。

这里特别注意:大模型看到的还是文本,并不是向量。向量只负责检索阶段的排序,生成阶段回归到普通的语言模型能力。这就是"检索增强生成"的完整含义:检索只是增强手段,生成仍然靠大模型。

2.3 Naive RAG 和 Advanced RAG 到底差在哪

最早期的 RAG 结构就是我上面说的那套流程,学术界叫 Naive RAG,意思是朴素版。它在简单问答场景下够用,但在复杂场景下问题很多:用户问题表述绕,检索不到;知识库里相关片段分散,召回的只有一部分;多个片段之间存在矛盾,模型不知道怎么取舍。

于是出现了 Advanced RAG 的概念。它在前处理阶段做了更多事情:比如用 LLM 做查询改写,把口语变成关键词;比如做 HyDE,让模型先根据问题生成一个假设性答案,再用这个答案去检索,实验下来能明显提升召回率;比如在索引阶段利用文档结构做层次化切分,让标题、章节、段落保持层级关系。

更进一步的还有模块化 RAG,把"检索"和"生成"拆成可编排的模块,甚至可以和 Agent(智能体)结合。比如系统先通过路由判断用户问的是内部知识还是常识,再决定是否调用 RAG;又比如多轮对话中先追问用户,把问题限定清楚后再检索。热词里有个"RAG 智能体",说的就是这种把检索能力当作工具交给 Agent 调用的形态。

下表可以快速看出朴素版和进阶版的差异:

维度Naive RAGAdvanced RAG
查询处理直接用原问题检索查询改写、HyDE、多轮理解
召回策略单一向量召回混合检索、多路召回
排序方式按向量相似度直接取 TopK召回后加 Rerank 精排
上下文处理简单拼接自动裁剪、去重、压缩
适用场景入门、Demo、小规模知识库生产级、复杂业务知识库

初学者不需要一上来就搞 Advanced RAG,先把朴素版跑通,再一个个加模块,这样出问题时才知道是哪一步救回来的。

3. 为什么先用 RAG 而不是大模型微调

3.1 微调是"改脑子",RAG 是"加外挂"

很多刚接触大模型的朋友会把 RAG 和微调放在一起比较,其实它们是两条完全不同的路线。大模型微调(Fine-tuning)是在原有模型参数的基础上,用一批有标签的数据继续训练,把新知识、新格式、新风格"写进"模型参数里。这个过程会改变模型内部的权重,相当于给员工做封闭式培训,让他在不查资料的情况下也能记住公司话术。

RAG 则完全不碰模型参数。它只是搭了一个外部检索通道,在每次问答时把相关内容临时送进上下文。用一句话区分:微调改变的是模型的"记忆",RAG 改变的是模型的"输入"。

这里要注意:参数化知识是有"知识截止日期"的。2023 年发布的模型,到了 2024 年你问它新增的规章制度,它大概率不知道。但 RAG 可以在文档里补充最新信息,模型就能基于最新文档回答。而且 RAG 的更新成本极低,文档变了,重新切块、重新索引一遍就行,无需重新训练。

微调也有它不可替代的价值:如果你想改变模型的说话风格、让模型学会特定格式输出、或者提升它执行某个任务的稳定能力,微调更合适。比如你想让模型在所有回答结尾都加上一段安全提示,这是输出习惯的改造,属于微调的强项,RAG 做不到。

3.2 决策表:什么场景选 RAG,什么场景选微调

选 RAG 还是微调,本质上是看你要解决的是"知识缺失"还是"能力改变"。

业务诉求推荐方案原因
企业内部规章制度、产品文档RAG更新频繁,需要溯源,错知识能快速修正
最新资讯、实时行情RAG微调数据准备时间长,赶不上变化
特定领域术语、行业黑话RAG + 微调协同RAG 提供知识,微调帮助模型熟悉术语表达
统一回复语气、固定输出格式微调这是行为层面的改变,RAG 很难约束
模型输出经常出现幻觉RAG有依据内容可参考,明显降低胡编概率
私有数据不能出域RAG + 本地部署本地向量库加本地模型,数据不离开内网

实际企业项目里,RAG 和微调并不是二选一。很多成熟方案是"先用 RAG 解决知识实时性问题,再用微调解决表达风格问题"。比如客服机器人:RAG 负责从最新知识库里查政策,微调负责让整个回答读起来像官方客服。这两者配合起来,基础模型的能力被发挥得更充分。

现在很流行的本地部署大模型、使用免费大模型 API,其实都是"模型接入方式"的问题,和 RAG 是正交关系。你可以用本地模型加 RAG,也可以用云端 API 加 RAG,流程没有任何本质区别。区别只在于数据是否出域、并发能力、以及模型本身的推理质量。

4. 零基础可复制的本地 RAG 知识库实战

4.1 本地环境准备与工具选型

前面讲了一堆原理,这部分我给出一个能直接跑通的本地 RAG 知识库最小实现。选型上我特意选了最简单、零门槛的组合:Ollama 加 Chroma。

Ollama 是一个本地大模型运行工具,可以一键拉取开源模型并在本机跑推理,同时也提供嵌入模型接口。Chroma 是轻量级向量数据库,支持持久化,非常适合作个人知识库和项目原型。整套方案不需要 GPU 也能跑(只是慢),模型我用相对轻量的 Qwen2.5 系列和 nomic-embed-text 嵌入模型。

先装依赖。系统需要有 Python 3.9 以上版本,然后命令行执行:

pip install ollama chromadb pypdf

接着拉取模型:

ollama pull qwen2.5:7b ollama pull nomic-embed-text

如果你的机器显存不够,可以把 7b 换成 3b 甚至更小的模型。嵌入模型 nomic-embed-text 本身很小,普通 CPU 也能跑。注意这里选 Ollama 不只是因为它方便,更重要的是它的嵌入接口和生成接口完全本地化,数据不出机器,适合企业内部做私有化验证。

4.2 完整代码:建库、检索、生成一次跑通

下面是一段完整的 Python 示例代码。它做的事是:读取本机上一个 txt 文件,按固定长度切分,逐段调用 Ollama 嵌入接口生成向量,写入 Chroma 数据库,然后实现检索和问答。

import ollama import chromadb # 1. 初始化向量库 client = chromadb.PersistentClient(path="./rag_demo") collection = client.get_or_create_collection(name="knowledge") # 2. 读取并切分文档 def split_text(text, chunk_size=300, overlap=50): chunks = [] start = 0 while start < len(text): end = start + chunk_size chunks.append(text[start:end]) start = end - overlap return chunks with open("manual.txt", "r", encoding="utf-8") as f: content = f.read() chunks = split_text(content) print(f"切分得到 {len(chunks)} 个片段") # 3. 逐段生成向量并入库 ids = [f"chunk_{i}" for i in range(len(chunks))] embeddings = [ ollama.embed(model="nomic-embed-text", input=chunk)["embeddings"][0] for chunk in chunks ] collection.add(ids=ids, embeddings=embeddings, documents=chunks) # 4. 检索函数 def search(query, top_k=5): qvec = ollama.embed(model="nomic-embed-text", input=query)["embeddings"][0] res = collection.query(query_embeddings=[qvec], n_results=top_k) return res["documents"][0] # 5. 问答函数 def ask(query): docs = search(query, top_k=5) context = "\n\n".join(docs) prompt = f"""请仅根据下面的资料回答问题。如果资料中没有相关信息,请直接说“资料中未提及”。 资料: {context} 问题:{query} """ resp = ollama.chat( model="qwen2.5:7b", messages=[{"role": "user", "content": prompt}], options={"temperature": 0.2} ) return resp["message"]["content"] print(ask("公司年假制度是什么?"))

这段代码最关键的地方有两点。一是"检索"和"生成"用的模型是分开的:nomic-embed-text 负责把文本变成向量,qwen2.5 负责最终回答,两者不能混用。二是 Prompt 里明确写了"仅根据下面的资料回答"以及"没有就直说",这是压制幻觉的第一道防线。

4.3 跑通之后,第一步怎么调优

能跑通只是开始,我在实际项目里发现,调优优先级这么排:先查召回质量,再调生成参数,最后才考虑换模型。

先看召回质量。把 ask 函数里的 search 结果单独打印出来,读一遍这 5 段内容是不是和问题相关。如果不太相关,优先调 chunk_size 和 top_k。如果相关但答案还是不对,再看 Prompt 有没有给足约束。比如 temperature 设置太高,模型会自由发挥,我一般控制在 0.1 到 0.3。如果召回的片段里包含大量无关内容,可以试试在前面代码里加一个简单 Rerank:让 qwen 对候选片段逐一打分,选出最相关的两段再送去生成。

嵌入模型的选择也很关键。nomic-embed-text 对英文效果好,中文场景我会优先换成 bge-m3 这类中文强的模型。在 Ollama 里同样可以用 ollama pull bge-m3 拉取。换模型后 embeddings 的维度可能变化,需要清空向量库重新建索引。

还有一个容易被忽略的点:建库时的文档质量决定系统上限。如果原始 PDF 是扫描件,没有经过 OCR 直接切分,那检索出来的全是乱码,后面再怎么调整也没用。所以索引前花时间做清洗,永远值得。

5. 实际项目中常见的 RAG 瓶颈与排查实录

5.1 五个能直接复用的排查思路

我在真正把 RAG 系统接到业务里时,踩过不少坑。下面这五个问题出现频率最高,对应的排查方法可以直接抄走。

现象常见原因排查方法
答案完全没提到相关知识文档没进库,或切分太粗导致检索不到用同一问题单独跑检索,打印返回片段
召回的相关片段不完整chunk_size 太小,概念被切开调整为 300-500 字,加 50 字 overlap
上下文里相关内容太少TopK 太小增大 K 到 8-10,再靠 Rerank 精排
模型无视检索内容、自由发挥Prompt 约束力不够,temperature 太高强化指令,把 temperature 降到 0.2
不同文档之间答案相互矛盾检索到了不同版本的资料建库时按文档来源打标签,检索时用元数据过滤

这里我特别想提示一个排查顺序:不要一上来就怀疑大模型不行。RAG 系统的成绩,70% 取决于索引和召回阶段。每次遇到效果差,先问一个简单问题:如果把这些检索结果给一个实习生看,他能写出正确答案吗?如果实习生都不能,那说明材料根本没找对,问题出在检索链路而不是生成链路。

5.2 "RAG 知识库能存图片吗"完整回答

这个热搜问题值得单独回答。标准文本型 RAG 知识库,存的是纯文本片段和向量,图片本身不能直接参与向量相似度检索。但在实际工程里,图片可以通过三种方式纳入知识库:

第一种是"图片转文字"式 RAG。用 OCR 把图片里的文字抽出来,或者用多模态大模型给图片生成一段详细描述,再把这段描述作为文档片段入库。用户提问时,系统检索到相关描述,回答里附上图片路径。这种方式实现成本最低,也是目前最主流的做法。

第二种是"多模态向量"式 RAG。用 CLIP 这类多模态模型,把图片本身也编码成向量,和文本向量放在同一个检索引擎里。用户提问的文本向量可以同时检索图片向量,直接找到视觉上相似的内容。这适合做以图搜图、商品匹配等场景,但工程复杂度比第一种高不少。

第三种是混合方案。把图片的相关文字、标签、描述全部入库,同时保留元数据字段,比如拍摄时间、产品型号、所属目录。业务层先通过数据库过滤缩小范围,再做向量检索,最终返回图文混合的答案。

顺带说一句,工业 AI 检测、服装检测这类场景,核心能力其实是目标检测和图像分类模型,不是 RAG 能直接替代的。RAG 在这些场景里的作用是辅助:比如让工人用自然语言查询缺陷历史记录、对比不同批次的检测参数,将检测结果和知识库里的标准文档关联起来。两者解决的问题不同,不要搞混。

5.3 效果评估才是长期维护的关键

RAG 系统做完不是终点,难的是持续迭代。我见过太多项目上线后凭感觉调参数,今天觉得这个好,明天觉得那个好,最后越调越乱。正确做法是建立一套小型的评测集:准备 30 到 100 条真实业务问题,每条标注标准来源片段和期望答案。每次改动切分策略、换嵌入模型、调整 Prompt,都把这套评测集跑一遍。

评测指标不需要太复杂,主要看三点:召回率(相关片段是否出现在检索结果中)、忠实度(模型回答是否忠于检索片段)、有用性(用户是否得到满意答案)。前两个可以自动化,第三个需要人工抽样。

我自己在实际项目里坚持一个习惯:每次发现问题就记录一条 badcase,写清楚"当时问题是什么、检索结果是什么、期望结果是什么"。积累到二三十条后,往往能看出一类共性原因,比如"所有关于价格的问题都召回到旧合同",这比零散地调参数有效得多。

最后再分享一个真实体会:RAG 不是越复杂越好。很多人一上手就堆查询改写、多路召回、Rerank、Agent 编排,结果系统里每个环节都有待调优,很难定位问题。我建议先跑通一个最简单的"切块-向量检索-拼 Prompt-生成"流程,让它稳定工作,再按评测结果一个一个往上加模块。还要记住,文档质量永远是地基,地基没打好,换再大的模型也救不回来。如果后续想把 RAG 用在更复杂的业务里,可以从"评估集先行、badcase 驱动迭代"这个方向继续扩展,它会让你少走很多弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进