上周帮一个做企业内部培训的客户搭知识库问答,测试的时候我把一份带表格的PDF直接塞给大模型,问它“去年华东区的销售考核口径是什么”,结果它理直气壮地开始编。后来换成RAG链路,先检索到对应的那几页文档,再把片段拼给模型生成,当场就给出了带引用位置的准确回答。那一刻我意识到,很多团队不是不会用大模型,而是没搞明白什么时候该让模型“自由发挥”,什么时候该给它接上一根“检索的管子”。
这篇东西,围绕RAG的原理、企业级落地和常见问题展开。RAG(Retrieval-Augmented Generation,检索增强生成)这两年已经成了知识库问答的标准解法,但我在实际项目里看到太多人把它当成“装个向量库就完事”的事情。结果就是召回不准、答案跑偏、更新了文档线上还是旧知识。这篇适合正在做知识库问答、RAG项目调优,以及想从零搭一套本地RAG验证想法的人。我会把链路拆开讲,把踩过的坑和排查思路一并写出来,尽量让看完的人能直接照着修正手头的方案。
1. 先理解RAG到底解决什么问题:外挂知识库,而不是重新训练模型
很多人第一次接触RAG,容易陷入一个误区:觉得它跟微调差不多,都是让模型“学会”新知识。实际上两者的本质完全不同。RAG的思路非常朴素——模型不懂,就让它在回答之前先去翻资料,翻到相关内容再作答,答案的源头是检索回来的文档,而不是模型参数里固化的记忆。微调则是把知识写进模型权重里,代价高、周期长,更新一次数据就要重新训一轮。
1.1 微调、长上下文、RAG:三条路怎么选
做企业级知识库,选技术路线前一定要先弄明白这三者的边界。
微调适合“改变模型的表达风格、输出格式、工具调用习惯”,比如让模型按公司规定的工单格式输出,而不是往里面灌事实性知识。把产品手册里的存量知识微调进模型,等于把出版社的印刷错误也刻进模板里,每次更新还得重印。
长上下文方案是把整份文档塞进上下文窗口。窗口不够长的历史问题正在被缓解,但注意力会稀释——文档一多,模型很容易被中间的冗长段落带偏,且每次问答都按整库文档量算token,成本肉眼可见地涨。
RAG的价值正好补在这两条路的中间:只把和问题相关的片段检索出来喂给模型,既控制了token,又能做到实时更新。今天改一条政策,索引重建一次,明天的回答就是新内容,不需要重新训练。这是它成为企业知识库默认方案的核心原因。
1.2 RAG主链路拆解:离线索引与在线检索生成
把RAG拆开看,其实是两段独立管线。
离线索引阶段做的是“备菜”:把原始文档解析成纯文本,按章节或语义切块,然后用embedding模型把每块转成向量,写入向量数据库。在线检索生成阶段则是“取菜下锅”:用户输入query,同样走embedding得到查询向量,去向量库召回最相近的TopK片段,把片段连同原始问题一起组装成prompt,交给大模型生成答案。
理解这条链路后会发现,RAG的质量不是由一个环节决定的,而是“取不到就答不好”的木桶效应。任何一个环节出问题——解析丢字、切块过碎、embedding不匹配、召回条数不对——最终都会表现为用户看到了一句错误的回答。
1.3 企业场景为什么更适合RAG
真实企业的知识库有几个共同特点:内容持续变化、需要权限控制、回答要可追溯。RAG天然满足这三点。文档更新只需重建对应索引,不用动模型;检索时可以按来源字段做权限过滤,不同部门看到不同知识;回答可以附上来源片段,出了问题能回查到是哪份文档提供的信息。
还有一个容易被低估的点:RAG落地不依赖昂贵的大模型推理资源。基座模型可以选通用开源模型,知识全在检索侧,换团队、换文档、换领域都不用重新训练模型,这在跨部门复制方案时尤其占优势。
2. 企业级检索链路的四个关键环节:切分、向量化、召回、重排
我见过太多团队把精力全放在模型选型上,却忽略检索侧。实际上企业级RAG的效果,八成由检索精度决定,生成只是把检索到的东西组织成话。下面四个环节是我做项目时必调的。
2.1 文档解析与文本切分:检索精度的第一瓶颈
先说解析。不要默认PDF转出来的文本是干净的。扫描件要OCR,带复杂表格的PDF常常乱序,Word里的公式、页眉页脚、目录会被一起扒下来变成噪声。我的经验是先做一轮清洗,把页码、重复页眉、超大空白行过滤掉,再用unstructured、Tika这类工具按文档类型分别处理。
切分策略直接决定“检索的基本单位”。单位太小,匹配到的碎片缺少上下文,答非所问;单位太大,噪声太多,而且超出embedding模型窗口的部分会被截断。我的默认值是chunk_size在300到500个token之间,overlap设为10%到20%,这个区间在多数中文业务文档上表现比较稳。
切分方式也不必死磕一种。重要性排序大概是:标题层级切分优于固定长度切分,语义切分优于纯递归切分,有父子结构的分块又优于平平无奇的单层分块。
| 切分方式 | 适用场景 | 经验 |
|---|---|---|
| 固定长度 | 日志、流水、无结构化文本 | 配合overlap,逻辑简单但容易截断语义 |
| 递归字符切分 | 常规Markdown、纯文本、代码 | LangChain里最常用,按分隔符逐层切割 |
| 标题层级切分 | 手册、政策、规范类长文档 | 按章节切块,保留章节路径,检索上下文更完整 |
| 父子分块 | 需要总结全文或跨段推理 | 小chunk用于精确匹配,父chunk用于喂给模型补充上下文 |
父子分块值得多说一句。它把小片段作为检索入口,命中后向上返回所属的大块内容,既能提高召回精确度,又能保证模型拿到完整的上下文。长文档场景下,这个方案比单纯调chunk_size更有效。
2.2 Embedding选型:中文场景别交智商税
Embedding模型的任务是把文本变成向量,让语义相近的内容在向量空间里靠得近。选型时要关注三个东西:向量维度、中文效果、是否开源可私有化部署。
中文业务场景,我的建议是从开源模型入手,BGE系列是个稳妥起点。bge-m3支持中英混合和长文本,bge-large-zh在中文语义匹配上表现出色,可以本地私有化部署。OpenAI的text-embedding-3系列效果不差,但对数据出境有要求的企业基本可以排除。另外电商、财税这些垂直领域,通用embedding常常不够用,要在自己的语料上做微调,这已经是进阶方案了。
Embedding还有一个隐蔽的坑:查询句和文档句的表达方式差异很大。用户问“报销流程怎么走”,文档里写的是“员工提交报销申请后由财务部门审核”。直接拿用户原话去检索,经常召回不到最相关的片段。解法是在检索前加一个“query改写”的步骤,让大模型先把口语化问题改写成与文档风格一致的检索词,再做embedding。
2.3 向量库与混合检索:只靠向量相似度大概率翻车
向量库选型要从部署成本和规模出发。
| 方案 | 类型 | 适用规模 | 备注 |
|---|---|---|---|
| FAISS | 库 | 百万级 | 简单,适合单机实验和快速验证 |
| Chroma | 库 | 小中型 | 开发友好,本地RAG常用 |
| pgvector | 数据库插件 | 中大型 | 复用PostgreSQL体系和运维 |
| Milvus | 分布式数据库 | 大规模 | 企业级标准选择,功能全但运维重 |
| Elasticsearch向量检索 | 数据库插件 | 中大型 | 已有ES可复用,便于做BM25+向量混合 |
检索不能全押在向量相似度上。原因很简单:embedding对齐的是语义,但用户query里的关键词可能根本不在语义最接近的文本里。混合检索是更稳的组合:BM25走关键词匹配,向量走语义匹配,两者各召回一部分,再用RRF(Reciprocal Rank Fusion)合并排序。
一个典型的调优经验:当用户问的是明确的名词、编号、型号,BM25召回的效果往往吊打向量召回;当用户问的是模糊的意图描述,向量召回才有优势。不把两条路都走一遍,等于主动放弃了一半的命中机会。
2.4 Rerank排序:把“沾边”变成“精准”
向量检索返回的TopK只能算“粗排”——相近但不一定都正确。企业级RAG必须加一道Rerank模型做“精排”。这个概念类比一下就是:海选投简历看学校和专业匹配度,终面才由业务主管判断这个人能不能干活。Rerank就是对海选入围者做最终判断。
Rerank模型直接拿query和候选文档做交叉编码,比双塔结构的向量相似度精细得多。bge-reranker-base这类开源模型足够覆盖多数业务场景。实践上,先向量召回Top50,再用Rerank压缩到Top3到Top5喂给模型,回答质量和token消耗都会更舒服。跳过Rerank的系统,往往表现为“召回的东西沾边,但答案总不够精准”。
3. 常见问题排查:从症状逆推病灶的完整思路
企业级RAG上线后,运维期的问题通常比开发期多。我把高频问题按症状分类,每类给一条排查链路,遇到问题按顺序查,基本不用乱试。
3.1 检索不到:先看TopK返回了什么,别急着改模型
症状是用户问一个明确问题,系统回答“没找到相关资料”或直接让模型自由发挥。不要第一时间怪生成模型,问题大概率出在检索侧。
排查步骤我一般这样走:
- 打开线上日志,看用户query改写后的实际检索串是什么。很多情况下是改写模块出bug,把原问题改废了。
- 把query用同一套embedding去查询向量库,打印Top5返回片段的原文和相似度分数。如果召回片段内容跟问题八竿子打不着,方向就有眉目了。
- 检查相似度阈值。有些团队在检索后加了一个相似度截断,阈值设太高会把有效片段全部滤掉。
- 检查切分是否合理。如果召回的片段只有一句话,多半是chunk_size太小,上下文不够;如果整篇文档被聚成一个chunk,又会因为向量平均化导致匹配不到精确位置。
这里最容易踩的坑是“改了embedding模型就以为能解决召回”。embedding确实影响召回,但大多数召回失败根因在数据清洗和切分,换模型只是心理安慰。
3.2 上下文明明有答案,为什么还答错或幻觉
这个症状更隐蔽:检索系统确实把包含答案的文档片段拼进了prompt,但模型返回的内容来源不明甚至凭空捏造。我排查的核心逻辑是先确认“真的拼进去了吗”,再确认“模型有没有从噪声里找到答案”。
先查prompt。很多框架默认只把TopK片段拼进去,不做总结、不加约束,导致模型在一堆冗余文本里迷路。处理方法是:给上下文加明确的区域边界,例如使用“以下资料来自公司知识库”这样的分隔标记;要求“只能依据上述资料回答,资料中没有的信息请直接说不知道”;并且把答案限制为一个可直接复制的段落而不是大段分析。
再查TopK污染。当K=5,其中只有1条相关、4条无关时,模型会被无关内容带偏,生成出那4条内容里的信息。解法是加上Rerank并降低最终入选条数,宁可只给2条高相关片段也不要给5条凑数的。
最后查查询文本与文档表达是否一致。用户问“怎么休假”,文档写“请假制度”,语义一致但词面不同。这种情况下,单纯改进检索不如在query改写环节做同义扩展,让系统在多个改写结果中并行检索。
3.3 知识更新了,线上还是旧答案:索引生命周期管理
这是生产环境最容易被骂的问题。文档已经更新,系统回答的还是旧内容,原因是索引层没有跟着更新。
RAG的索引和缓存一样,有版本、有生命周期。企业级方案里必须做一套管理机制:文档进入时计算哈希,对比向量库里的来源字段;文档更新后触发增量重建,而不是全量重灌;对明确删除的文档要走“下线”流程,确保旧向量从库里移除,而不是留着占用检索位。
我见过最典型的错误是:开发环境全量重建索引,生产环境却忘了设定时任务,导致线上库和真实文档长期脱节。排查这类问题,先查索引的构建时间和文档的更新时间是否对齐,再查更新任务是否真的成功执行。一套可观测的索引日志,远比你事后猜原因来得快。
3.4 多轮对话与跨文档问题:查询改写和RAG智能体
用户问“上次说的那个报销政策,现在还能用吗?”,系统检索“上次说的那个”注定失败。多轮对话场景下,每轮用户问题都带有代词和省略,必须先把对话历史转换成独立检索词。
跨文档问题则是另一个层次:用户问“我们产品的部署要求和配置端口分别是什么”,答案分散在安装手册和安全白皮书两份文档里。单次检索很难同时命中两个主题,这时候需要引入“RAG智能体”式的编排,让它先规划子问题,再逐个检索、汇总、最终回答。
“RAG智能体”在这个语境下不是什么神秘架构,本质上就是控制流加多轮检索的组合:给大模型一个工具调用的接口,它可以先决定检索哪类文档,再根据初步结果决定是否补充第二路检索。它解决的是“单次检索只能回答单点问题”的瓶颈,但对工程能力要求高,核心逻辑要放在检索编排上,而不是放在“看起来很智能”的Agent外壳上。
3.5 企业级权限隔离:元数据过滤不可省
权限隔离这个问题,中小团队很容易忽略,直到被审计问住。同一个RAG系统服务多个部门时,如果检索时不按来源过滤,一个部门的关键文档可能被另一个部门的员工检索到。
解决方式是在索引阶段给每个chunk写入元数据字段,如部门、密级、文档类型,在检索阶段追加条件过滤。这意味着向量数据库必须支持带过滤条件的混合检索,而不能只是简单的相似度查询。权限过滤放在检索前而不是回答后,这是一个原则性问题:先让权限外的内容不进上下文,再谈生成正确。
4. 零基础本地RAG复现:Ollama+常用工具集搭建私有知识库
纸上谈兵容易,动手才是硬道理。这里给出一条零基础也能复现的路径:用Ollama跑模型,用文本拆解工具切文档,用本地向量库存储和检索。全部免费,不需要GPU也能跑起来,适合先验证RAG原理。
4.1 为什么选Ollama:模型管理简单,离线可用
Ollama相当于一个本地模型管家,一条命令下载模型,一条命令起服务。企业环境里数据不能出内网,Ollama直接提供本地推理,不依赖外部API。开头提到的热词“ollama + 简易本地 rag 知识库”指的就是这套方案。
硬件方面,不需要动辄几十GB的显存。embedding模型很小,CPU就能跑。生成模型选7B级别的量化版本,16GB内存的机器可以流畅运行。先跑通再谈扩大规模,这是省钱又省力的策略。
4.2 环境准备与模型下载
先把Ollama装上,然后拉两个模型:
# 安装Ollama后,服务默认跑在11434端口 ollama pull nomic-embed-text ollama pull qwen2.5:7b一个是embedding模型,用于文本向量化;一个是生成模型,用于最终回答。选qwen2.5系列是因为中文能力扎实,量化后对硬件友好。“nomic-embed-text”则是对英文更友好,中文场景可以换bge-m3,但Ollama拉取方便性上nomic胜出。先跑通,后面再换模型不迟。
另外准备文本拆解工具。本地离线优先的方案是unstructured或PyMuPDF。unstructured能解析PDF、Word、HTML,PyMuPDF轻量快速,适合处理常规PDF文本层。
4.3 最小可运行代码骨架
下面这段代码是最小闭环:读入文本文件、切块、调Ollama embedding入库,再做查询。
from langchain_community.document_loaders import TextLoader, PyPDFLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_community.embeddings import OllamaEmbeddings from langchain_chroma import Chroma # 1. 读取文档 loader = TextLoader("knowledge_base/a.txt") docs = loader.load() # 2. 切分 splitter = RecursiveCharacterTextSplitter(chunk_size=300, chunk_overlap=50) chunks = splitter.split_documents(docs) # 3. 向量化 + 入库 embedding = OllamaEmbeddings(model="nomic-embed-text") vectorstore = Chroma.from_documents( documents=chunks, embedding=embedding, persist_directory="./chroma_db" ) # 4. 检索 query = "公司年假规定是几天" retrieved = vectorstore.similarity_search(query, k=3) # 5. 拼装 prompt 并生成 from langchain_ollama import OllamaLLM context = "\n\n".join([d.page_content for d in retrieved]) prompt = f"""请仅依据下面给出的资料回答问题。 资料中没有的信息,请回答“知识库中未找到相关内容”。 资料: {context} 问题:{query} """ llm = OllamaLLM(model="qwen2.5:7b") print(llm.invoke(prompt))这段代码能跑通“单文件知识库”的最小闭环。真实项目里要在这个骨架上扩展多文件扫描、元数据写入、混合检索和权限过滤,但原理是一样的。
4.4 跑通后必须做的冒烟测试与常见报错
跑通首条问答只是起点,我会立刻做三条冒烟测试:
- 问一个文档里明确写着的细节,比如“报销额度上限是多少”,看模型是否准确引用而不是自己编。
- 问一个需要跨两段内容总结的问题,验证chunk上下文拼接是否足够。
- 问一个文档里不存在的问题,看模型会不会诚实回答“未找到”,而不是硬编一个答案。
常见报错也顺手排一下:Ollama服务没启动时,Python端会报连接拒绝,先执行“ollama serve”确认端口在监听;模型名写错会报not found,执行“ollama list”核对名字;Chroma依赖版本冲突多发生在langchain升级后,建议锁定版本一组一组装,不要一次性升到最新。
5. 框架选型、多模态边界与RAG瓶颈
走到这一步,你已经不是刚入门的水平了。接下来要考虑的是:项目继续变大,技术栈怎么选;业务方问“知识库能不能存图片”怎么答;以及RAG这条路还有哪些瓶颈和演进方向。
5.1 RAG框架选型参考
框架没有绝对好坏,只有适不适合你的团队和场景。
| 框架 | 语言 | 定位 | 适合场景 |
|---|---|---|---|
| LangChain | Python | 全功能链式编排 | 灵活度要求高、技术团队能力强 |
| LlamaIndex | Python | 数据索引与检索 | 文档规模大、检索结构复杂 |
| Dify | 可视化 | 低代码平台 | 业务快速验证,非技术团队可上手 |
| LangChain4j | Java | Java生态RAG框架 | Java技术栈,愿意接受较新的技术 |
| 自研检索服务 | 任意 | 定制化 | 已有搜索团队,需要深度耦合业务 |
Java团队要注意,LangChain4j这类Java生态框架正在快速补位,对应“langchain4j easy rag”这类热词。早期Java做RAG很痛苦,需要自己拼装组件,现在的LangChain4j已经把向量库接入、文档加载、对话管理封装得比较完整,但生态成熟度仍不如Python系。
选型终究不是选一个“最流行的”,而是选一个你能hold住且方便换掉核心组件的。我会特别看重框架是否能方便替换embedding模型和向量库,避免被一家厂商锁死。
5.2 “RAG知识库能存图片吗”的真正答案
这个热搜问题,网上的答案大多模棱两可。从原理上回答:RAG是一条“文本检索+文本生成”的链路,原生不支持直接存图片字节。但知识库要支持图片,从来不是靠“把图片塞进向量库”来实现的。
实践上有两条路线。
路线一是把图片转成文本再进RAG,这是企业场景最常用也最稳妥的方案。PDF里的拍照件、截图、流程图,先做OCR提取文字,再做图片描述生成图片语义,把两类文本一起写入索引。用户提问时检索到的是“描述这张图的文字”,回答质量取决于OCR准确度和描述文本的详细程度。
路线二是多模态向量检索,直接用CLIP这类模型把图片和文本编码到同一向量空间,实现“用文字搜图片”或“用图片搜图片”。这条路能保留图片本身的视觉语义,但工程链路重、评估复杂、生成端还要有多模态大模型配合,成本高不少。
我的建议是:90%的企业文档场景走路线一就够了,先把“图里的字被搜到”做好,再谈“图片的视觉内容被理解”。
5.3 企业级RAG真正的瓶颈:可观测性与评估
当年投资效果好不好看收益率,RAG系统好不好要看评估指标。多数团队上线后只靠“人工聊几个问题觉得行”来判断系统质量,这在生产环境是完全不够的。
最能说明问题的一组指标是:检索命中率(答案是否能在TopK里找到)、生成忠实度(回答是否严格基于检索片段)、整体准确率(人工抽样的答案正确比例)。上线前必须构建一套样本集,至少覆盖常规问答、长尾问答、无答案三类,并把它固化下来做回归测试。改任何一个环节前先跑老样本,防止“修好一个问题、带崩一片回答”。
可观测性也一样。每条线上问答都要能回溯到:query改写成了什么、召回TopK分别是什么、Rerank后保留了哪几条、最终prompt长什么样。没有这套日志,出了问题只能靠用户截图猜,排查效率低到怀疑人生。
5.4 从RAG到GraphRAG:关系型问题的下一步
经典的RAG框架长于“语义相似”,短于“关系推理”。“A事业部下面的三个团队分别负责什么,他们的考核指标之间有什么关系”,这类问题用纯向量检索往往答不准,因为答案藏在文档间的关联结构里,而不是单一文本块中。
GraphRAG就是针对这个问题的演进:先抽取文档中的实体和关系,构建知识图谱,再把图谱结构和文本片段结合进检索过程。对应的“ontology rag”热词,本质上是强调以本体论的方式建模领域概念和关系,再用术语关系约束检索范围。对业务关系复杂的企业,这条路值得关注,但不要一上来就折腾图谱——先把传统RAG的检索精度做到位,再看是不是真有关系推理的硬需求。
写到最后,分享一点做RAG项目的实际体会。这个技术的边界比你想象得清晰:检索侧决定系统上限,模型侧决定表达质量,数据治理决定长期稳定性。我见过太多团队把精力花在换模型、调prompt上,最后发现拖后腿的是文档解析和索引更新脚本。先小规模跑通一个完整链路,再逐步扩规模、加评估、做可观测,这套“先窄后宽”的打法,比一开始就铺大摊子靠谱得多。如果你正在做一个知识库方案,先把检索日志打开,看看那些用户真正问的问题有没有被召回,你会比看十篇评测文章都更有收获。