1. 先搞清楚这套东西到底在解决什么问题1.1 从“搜不到”到“问出来”的转变很多人第一次接触 AI 知识库脑子里想的其实是“我有一堆 PDF能不能直接问它问题”。这个直觉是对的但中间隔着一整套工程链路。传统做法是把 PDF 丢进文件夹靠文件名和系统自带的全文搜索找内容问题是 PDF 里的文字一旦被扫描成图片、或者排版稍微复杂一点搜索就废了。更麻烦的是你搜“第三季度营收”只能匹配到包含这几个字的页面没法回答“第三季度营收同比变化的原因是什么”这种需要跨段落理解的问题。RAG 要干的事情就是把“搜索”升级成“问答”。它的全称是 Retrieval-Augmented Generation检索增强生成。拆开看就三件事先把你的文档切成小块存起来用户提问时从库里捞出最相关的几块再把这几块内容和问题一起交给大模型让它基于这些材料组织答案。整个过程里大模型不是凭记忆瞎编而是拿着你给的“小抄”回答这就是它比直接问 ChatGPT 靠谱的地方。我见过太多人一上来就装 LangChain、配向量库、调 embedding 模型结果卡在 PDF 解析上——扫描件全是乱码表格被拆得七零八落后面再怎么调都是白费。所以这篇东西我打算按真实的学习顺序来写先弄明白每个环节在干嘛再动手跑通最小闭环最后才是优化和扩展。零基础也能跟但前提是你得愿意动手光看是看不明白的。1.2 适合谁看不适合谁看这套路线适合三类人一是手里攒了大量 PDF 资料论文、手册、报告想快速检索的二是想给自己产品加个“文档问答”功能的开发者三是纯粹想搞懂 RAG 是怎么回事、不想被各种框架名词绕晕的学习者。不适合两类人指望复制粘贴几行代码就得到一个完美知识库的以及不愿意花时间理解数据处理的。RAG 的瓶颈从来不在模型而在你的数据本身。垃圾进垃圾出这句话在 RAG 里体现得淋漓尽致。2. 核心概念拆解别被名词吓住2.1 Embedding 到底是什么为什么它这么关键Embedding 这个词翻译成“嵌入”其实挺抽象的。你可以把它理解成给每段文字发一个“坐标”。假设我们把所有中文句子都映射到一个三维空间里“今天天气不错”和“今天阳光很好”这两个句子的坐标会非常接近而“今天天气不错”和“红烧肉的做法”坐标就离得很远。这个坐标就是一个向量比如[0.23, -0.11, 0.87]维度可能是 768 维、1024 维甚至更高。为什么需要这个因为计算机没法直接比较两段文字“意思像不像”。它只能算数字。有了向量之后比较两段文字的相关性就变成了算两个向量的余弦相似度数学上非常成熟。用户问“怎么申请报销”系统把这个问题也转成向量然后去库里找坐标最接近的那些文本块这就是语义检索的底层逻辑。这里有个坑要提前说embedding 模型分中文和英文的也分通用和特定领域的。你拿一个主要用英文语料训练的模型去处理中文法律文书效果会差得离谱。热词里提到的“embedding模型排行”之所以火就是因为选错模型后面全白搭。我的建议是中文场景优先考虑 BGE 系列或者 M3E 系列它们在中文语义相似度任务上表现稳定而且有不同大小的版本可以权衡速度和精度。2.2 Chunk 切分最不起眼但最容易翻车的一步Chunk 就是“文本块”。你把一篇 50 页的 PDF 直接丢给大模型它要么超出上下文限制要么因为内容太多而抓不住重点。所以必须切。但怎么切切多大这里面全是细节。最粗暴的做法是按固定字数切比如每 500 个字一块。问题是它可能把一句话从中间切断“本公司第三季度营收为”在一块里“5.2 亿元同比增长 18%”在另一块里。检索的时候只捞到前半句答案就残了。稍微好一点的做法是按段落切遇到换行就断开。但 PDF 解析出来的文本经常没有正确的换行段落边界是乱的。我实测下来比较稳的策略是“递归切分”先按段落切如果某段还是太长再按句子切句子还长就按字数硬切同时设置一个重叠区域overlap。比如每块 500 字相邻两块重叠 50 到 100 字。这个重叠的作用是防止关键信息刚好落在切割线上被一分为二。重叠太多会浪费存储和检索时间太少又起不到保护作用50 到 100 字是个比较舒服的区间。还有一个容易被忽略的点表格和图片。热词里有人问“rag知识库能存储图片嘛”答案是能存但检索逻辑不一样。图片需要先用 OCR 或者多模态模型转成文字描述再走同样的 embedding 流程。表格则最好在切分时保留表头否则单独一块数据没有列名大模型根本看不懂。2.3 RAG 和 LLM Wiki 的关系热词里反复出现“llm wiki”和“rag和llm wiki”这两个概念经常被混着用。简单说LLM Wiki 更像是一种产品形态——把知识库包装成 wiki 的样子用户既能浏览条目也能提问。RAG 是背后的技术手段。你可以用 RAG 做一个 LLM Wiki也可以用 RAG 做一个客服机器人技术内核是一样的只是前端交互不同。至于“ontology rag”和“graphrag”那是更进阶的玩法。普通 RAG 是把文本切块后平铺存储检索时只看单块的相关性。GraphRAG 会先抽取出实体和关系构建一个知识图谱检索时能沿着关系链找到关联信息。比如问“张三负责的项目有哪些”普通 RAG 可能只找到提到张三的那一块而 GraphRAG 能顺着“张三-负责-项目A”这条边找到项目A的详情。代价是构建成本高很多零基础阶段先不用碰。3. 从零搭建的最小可行路线3.1 环境准备别一上来就搞复杂的我建议第一版就用 Python 加几个核心库不要碰 LangChain 这种重框架。原因很简单框架封装了太多东西出问题你根本不知道是哪一步挂了。先用最原始的方式跑通理解每个环节的输入输出后面再用框架提效。需要装的东西不多pip install pypdf sentence-transformers chromadb openaipypdf负责从 PDF 里抽文字sentence-transformers提供 embedding 模型chromadb是一个轻量级向量数据库openai用来调大模型生成答案。如果你不想用在线 API可以用ollama在本地跑模型热词里“ollama 简易本地 rag 知识库”说的就是这个路子。注意chromadb 在 Windows 上偶尔会有编译问题如果装不上可以换成faiss-cpu功能类似只是 API 不一样。3.2 第一步把 PDF 变成干净的文字这一步的目标是把 PDF 里的文字提取出来并且尽量保留段落结构。代码不复杂from pypdf import PdfReader def extract_text(pdf_path): reader PdfReader(pdf_path) full_text [] for page in reader.pages: text page.extract_text() if text: full_text.append(text) return \n.join(full_text)跑完之后你大概率会发现两个问题一是扫描件提取出来是空的二是表格变成了乱七八糟的字符。扫描件需要额外做 OCR可以用pytesseract配合pdf2image但那是另一个话题了。表格的话如果 PDF 本身是电子版而非扫描版可以试试pdfplumber它对表格的支持比 pypdf 好一些。我自己的经验是先拿一份文字版 PDF 跑通全流程确认没问题了再去处理扫描件和复杂表格。不要一上来就挑战最难的文档那样容易卡住然后放弃。3.3 第二步切块并生成向量拿到全文之后按前面说的递归策略切块。这里给一个简化版实现def split_text(text, chunk_size500, overlap80): chunks [] start 0 while start len(text): end start chunk_size chunk text[start:end] chunks.append(chunk) start end - overlap return chunks实际用的时候最好在切之前先按\n\n分段对每段判断长度太长再切。这样能最大程度保留语义完整性。切完之后用 embedding 模型把每个 chunk 转成向量from sentence_transformers import SentenceTransformer model SentenceTransformer(BAAI/bge-small-zh-v1.5) chunks split_text(full_text) embeddings model.encode(chunks)bge-small-zh-v1.5是一个中文小模型速度快效果对于入门够用。如果追求更高精度可以换bge-large-zh-v1.5但显存占用和计算时间会明显上升。热词里“embedding模型排行”经常提到这两个选哪个取决于你的硬件和延迟要求。3.4 第三步存进向量库并实现检索把向量和对应的文本块一起存进 chromadbimport chromadb client chromadb.Client() collection client.create_collection(my_knowledge) collection.add( documentschunks, embeddingsembeddings.tolist(), ids[fid_{i} for i in range(len(chunks))] )检索的时候把用户问题也转成向量然后查最相似的几块def search(query, top_k3): query_embedding model.encode([query]).tolist() results collection.query( query_embeddingsquery_embedding, n_resultstop_k ) return results[documents][0]top_k设多少一般 3 到 5 比较合适。太少可能漏掉关键信息太多会引入无关内容干扰大模型。这个参数可以后面根据实际效果调。3.5 第四步拼装 prompt 并调用大模型检索到相关文本块之后把它们和用户问题拼成一个 promptdef ask(query): contexts search(query) context_text \n\n.join(contexts) prompt f基于以下资料回答问题如果资料中没有相关信息就说不知道。 资料 {context_text} 问题{query} 答案 # 调用大模型 API response call_llm(prompt) return response这个 prompt 模板里有一句很关键的话“如果资料中没有相关信息就说不知道。” 这是为了防止大模型在检索结果不相关时强行编造答案。我试过不加这句话模型会非常自信地胡说八道加了之后至少它会承认自己不知道。到这里一个最小可用的 RAG 系统就跑通了。从上传 PDF 到能回答问题核心代码不超过 100 行。但能跑通和好用之间还差着大量的调优工作。4. 效果调优从“能用”到“好用”4.1 检索命中率上不去先查这三个地方热词里“rag hit rate”被反复提到说明这是大家共同的痛点。检索命中率低通常不是模型的问题而是数据的问题。按我的排查顺序先看切块大小是否合理再看 embedding 模型是否匹配语料最后看检索数量是否够用。切块太大一块里混了多个主题向量表示会变得模糊检索时反而不容易命中。切块太小信息不完整捞到了也没法回答问题。我一般会拿几个典型问题做测试手动看检索出来的块是否真的相关。如果经常捞到不相关的就把块调小一点如果经常捞到相关但信息不全的就把块调大或者增加重叠。embedding 模型这块中文场景下 BGE 系列确实比 OpenAI 的 text-embedding-ada-002 在中文语义相似度上表现更好。但如果你的是中英混合文档可能需要测试多个模型再决定。热词里“embedding模型排行”之所以受关注就是因为这个选择对最终效果影响巨大而且没有万能答案。4.2 大模型答非所问问题可能出在 prompt 上检索没问题但答案不对八成是 prompt 没写好。我踩过的坑包括资料块之间没有明确分隔模型分不清哪段是哪段没有告诉模型“只基于资料回答”它就自由发挥了问题太模糊模型理解偏了。改进方法是在 prompt 里加明确的指令和格式。比如你是一个严谨的助手。请仅根据下面提供的资料回答问题。 如果资料不足以回答请直接说“资料中没有相关信息”。 不要编造资料中不存在的内容。 资料片段 [片段1] --- [片段2] --- 问题xxx用---分隔不同片段比空行更清晰。加上“不要编造”这种明确禁令比单纯说“基于资料”有效得多。4.3 什么时候该上框架什么时候不该LangChain、LlamaIndex 这些框架确实能省很多代码但它们也引入了抽象层。我的建议是当你已经用原生代码跑通过一遍清楚每个环节在干嘛之后再用框架提效。如果一上来就用框架遇到问题你连从哪查都不知道。热词里“langchain4j easy rag”和“spring ai rag”是 Java 生态的方案适合 Java 开发者。Python 生态的话LlamaIndex 在 RAG 场景下比 LangChain 更专注一些API 设计也更直观。但核心逻辑是一样的加载、切分、嵌入、存储、检索、生成。5. 常见问题速查与避坑指南5.1 问题排查表现象可能原因排查方向PDF 提取出来是空白扫描件没有文字层需要 OCR 处理检索结果完全不相关embedding 模型不匹配语料换中文专用模型测试答案包含资料中没有的内容prompt 没有限制编造加“仅基于资料回答”指令回答总是“不知道”检索没捞到相关块检查切块大小和 top_k处理速度特别慢模型太大或块太多换小模型或减少块数量表格内容检索不到表格被切散或没保留表头单独处理表格或保留表头5.2 几个我踩过的坑第一个坑是忽略 PDF 里的页眉页脚。很多 PDF 每页都有相同的页眉比如书名或章节名。这些内容被重复提取后会在向量库里产生大量几乎相同的块检索时经常捞到这些无意义的重复内容。解决办法是在提取后做一次去重或者用规则过滤掉每页开头结尾的固定文本。第二个坑是 embedding 模型第一次使用时需要下载国内网络环境可能很慢甚至失败。可以提前把模型下载到本地用SentenceTransformer(本地路径)的方式加载。HuggingFace 有镜像站可以用具体方法搜一下就有。第三个坑是 chromadb 默认用的是内存存储程序一关数据就没了。要持久化的话创建 client 时指定persist_directory参数。这个在官方文档里写得不太显眼我第一次用的时候重启程序发现库空了还以为是 bug。5.3 关于“rag瓶颈”的一些观察热词里“rag瓶颈”也是个高频词。以我的经验RAG 系统的瓶颈通常不在生成端而在检索端。大模型现在都很强给它正确的资料它就能给出不错的答案。难的是从海量文档里精准捞出那几块真正相关的。提升检索质量的手段包括用更好的 embedding 模型、优化切块策略、加 rerank 模型做二次排序、用混合检索关键词加语义。其中 rerank 的性价比很高就是在向量检索捞出 top 20 之后用一个专门的 rerank 模型对这 20 个结果重新打分选出最相关的 3 个给大模型。这一步能明显提升最终答案的质量代价是增加一点延迟。6. 后续可以往哪些方向扩展跑通基础版之后你可以根据实际需求往几个方向走。一是支持更多格式除了 PDF 还有 Word、Markdown、网页等每种格式的解析方式不同。二是加多轮对话能力让用户可以追问这需要把历史对话也纳入上下文管理。三是做权限控制不同用户能访问的知识库范围不同。四是接入即时通讯工具或网页前端让它真正能被用起来。热词里提到的“agentic rag”和“rag智能体”是更前沿的方向让 AI 自己决定什么时候检索、检索什么、要不要多轮检索。这些目前还在快速演进中零基础阶段先把基础 RAG 跑稳后面再逐步深入。我个人在实际操作中的体会是RAG 这件事百分之七十的功夫花在数据准备上百分之二十花在检索调优上真正跟大模型相关的部分可能只占百分之十。很多人本末倒置一直在换更大的模型却不肯花时间把 PDF 解析干净、把切块策略调合理。把基础打牢后面的事会顺很多。