基于Notebook的RAG实战指南:从零搭建检索增强生成知识库 📅 发布时间:2026/8/31 11:34:38 👁 浏览次数: 有不少读者问我想看一个能把 RAG 从零跑通的 Notebook 项目而不是只看概念图。RAG 刚火的时候大家关注的是“它是什么”现在更多人关注的是“我的文档放进去怎么才能真的搜得准、答得对”。这篇文章就围绕一个RAG Refresher Notebook来展开。我会先讲清楚 RAG 的核心链路再带你用一个 Jupyter Notebook 把整套流程完整跑一遍包括文档加载、文本切分、向量化、检索、重排、生成回答。最后还会补充知识库指标的理解方式、常见报错排查以及让 RAG 更精准的工程经验。不管你是刚接触 RAG 的新手还是已经搭过 demo 但效果不理想的开发者这份笔记都值得收藏对照实践。1. 为什么要用 Notebook 复现 RAG1.1 RAG 是什么解决什么问题RAG 全称是 Retrieval-Augmented Generation也就是“检索增强生成”。通俗地说大语言模型本身的知识有截止时间也不知道你私有的业务文档。RAG 的思路是在模型回答之前先从你的知识库里把相关片段检索出来再把这些片段和用户问题一起交给大模型让模型基于检索到的内容组织答案。典型流程如下用户问题 - 文本向量化 - 向量库检索 - 召回相关片段 - 拼接提示词 - 大模型生成回答RAG 解决的核心问题是模型不知道你公司的制度、产品手册、私有 PDF 内容。模型知识有截止日期无法回答最新信息。模型容易“一本正经地胡说八道”RAG 用检索结果约束答案来源。相比重新训练模型RAG 更新知识成本低得多换一个文档就能更新知识。1.2 为什么 Notebook 是复现 RAG 的好工具Jupyter Notebook 最大的特点是“按单元格运行、结果留在页面里”。这对 RAG 学习非常有帮助。举例来说加载完文档后你可以立刻打印前 500 个字确认文档读对了。切分完文本后可以打印几个 chunk看切得是否合理。检索之后可以把命中的文本打出来人工判断“召回结果到底准不准”。最后再调用大模型生成回答整个过程是透明的每一步都能看到中间结果。这种“逐步可视化”的调试方式比直接写一个.py脚本跑完要好理解得多。所以我一直建议做 RAG 原型验证阶段优先用 Notebook跑通了再工程化封装成服务。1.3 这份 Notebook 会带你完成什么这份RAG Refresher Notebook的目标不是做一个生产级系统而是帮你把 RAG 的完整链路亲手实现一遍。完成之后你将掌握文档加载与解析的常见方式。文本切分的原理和参数含义。使用开源 Embedding 模型做向量化。使用 Chroma 作为本地向量库。实现向量检索与简单的重排逻辑。调用大模型生成最终回答。理解 RAG 知识库评估指标。2. 环境准备与工具链2.1 Anaconda、Jupyter Notebook 和 Lab 怎么选做 Python 数据类开发Anaconda 是目前最省心的发行版。安装 Anaconda 后自带的conda可以方便地创建虚拟环境。很多新手会问Jupyter Notebook 和 JupyterLab 到底有什么区别简单来说Jupyter Notebook 是经典的单文档交互界面适合逐格运行代码。JupyterLab 是新一代 IDE 风格界面支持多标签、拖拽文件、终端、文件管理功能更全面。两者底层内核一样代码、.ipynb文件互相兼容。如果你只是打开一个.ipynb快速运行Notebook 够了。如果你要同时看文档、调代码、开终端更推荐 JupyterLab。在 Anaconda Prompt 中启动命令如下# 启动 Jupyter Notebook jupyter notebook # 启动 JupyterLab jupyter lab2.2 创建虚拟环境并安装依赖建议为 RAG 项目单独创建一个虚拟环境避免依赖冲突。conda create -n rag_notebook python3.10 -y conda activate rag_notebook然后安装依赖。这里需要注意版本本示例以常见稳定版本为例不同版本 API 可能有差异请以你安装后的实际版本为准。pip install jupyter pip install langchain langchain-community langchain-text-splitters langchain-huggingface pip install chromadb sentence-transformers pip install python-docx pypdf unstructured pip install openai如果你的网络较慢建议使用国内镜像源安装例如pip install langchain chromadb sentence-transformers -i https://pypi.tuna.tsinghua.edu.cn/simple2.3 Notebook 侧边栏标题总览长 Notebook 写完后右侧的“目录 / 大纲”视图非常有用。JupyterLab 默认有“Table of Contents”插件可以点击左侧目录图标查看所有 Markdown 标题。如果你使用的是 Jupyter Notebook 7打开 Notebook 后在视图菜单中开启“Show Table of Contents”即可。这样你可以像看文章目录一样快速跳转到不同章节。3. RAG 核心链路拆解在写代码之前先把 RAG 的每个环节拆开讲清楚。理解了每个环节的作用后面看代码才不会晕。3.1 文档加载与解析RAG 的第一步是读取文档。文档可能是 PDF、Word、Markdown、HTML 或纯文本不同格式需要不同的解析器。常见文档加载器如下文档类型常用加载器说明TXT / MarkdownTextLoader最简单读成纯文本PDFPyPDFLoader按页读取 PDF 文本WordDocx2txtLoader读取 .docxHTMLBSHTMLLoader解析 HTML 标签这里最常见的坑是PDF 解析出来的文本可能丢失结构。比如一份带标题层级、表格、多栏排版的 PDF用 PyPDF 直接抽取结果可能是混乱的。如果你的文档是合同、技术规范等强结构文档建议优先使用保留了标题结构的信息源。例如先把 PDF 转成 Markdown或者使用 Unstructured 这类保留更多结构的工具。3.2 文本切分切分是为了让模型只关注相关片段。如果整篇文档都塞给模型会超出上下文限制也会引入大量噪声。最常用的是递归字符切分器RecursiveCharacterTextSplitter。它的策略是先用一个大的分隔符切如果切出来的块还是太长就换更小的分隔符继续切。核心参数chunk_size每个块的最大字符数。chunk_overlap相邻块之间的重叠字符数避免关键信息正好被切在边界上。举个例子from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size500, chunk_overlap50, separators[\n\n, \n, 。, , , , ] )需要注意的是中文的切分和英文不太一样。英文可以按空格切中文更适合按句号、感叹号、问号等句子边界切。上面把中文标点加进了 separators就是为了减少句子被硬切的风险。3.3 向量化与向量库向量化的作用是让文本变成计算机能计算相似度的数字数组。你可以把 Embedding 模型理解为“翻译官”它把一段文本映射成一个高维向量语义相近的文本向量距离也近。常用的本地 Embedding 模型BAAI/bge-small-zh-v1.5BAAI/bge-m3shibing624/text2vec-base-chinese使用开源模型的好处是数据不出内网适合企业私有化场景。缺点是中文效果需要测试不同模型差距很大。在 Notebook 中加载 HuggingFace Embedding 模型from langchain_huggingface import HuggingFaceEmbeddings embeddings HuggingFaceEmbeddings( model_nameBAAI/bge-small-zh-v1.5, encode_kwargs{normalize_embeddings: True} )normalize_embeddingsTrue表示对向量做归一化。归一化之后用内积计算相似度等价于余弦相似度这在用 Chroma 检索时更稳定。向量库的作用是存储向量并提供相似度检索。Chroma 是一个非常适合做原型验证的轻量级向量库支持本地持久化不需要额外启动服务。3.4 检索与重排检索阶段系统把用户问题也转成向量然后在向量库中找到最相似的 Top K 个片段。但向量检索不一定完美。可能出现两种问题召回结果语义相关但关键信息缺失。召回结果顺序不合理真正有用的片段排在了后面。这时候就需要重排Rerank。重排模型会结合“用户问题 候选文档”做更精细的匹配打分把最相关的片段提到前面。常见的重排模型有BAAI/bge-reranker-base、bge-reranker-v2-m3等。3.5 生成回答最后一步是把检索到的文档片段和用户问题拼接成 Prompt交给大模型。一个简洁的 Prompt 模板如下你是一个知识库问答助手。请仅根据以下资料回答问题。 如果资料里没有相关内容请直接回答“知识库中未找到相关信息”。 资料 {context} 问题{question}这里要注意不要给模型太多自由发挥的空间。RAG 的核心价值是“有依据地回答”如果 Prompt 中没有强调“仅根据资料回答”模型很可能又用训练知识自由发挥导致回答“看起来对但没依据”。4. 完整实战用 Notebook 搭建一个最小 RAG下面我们会走进一个完整的 Notebook。建议你在本地按顺序创建新单元格运行而不是一次性粘贴全部代码。4.1 项目结构与准备数据建议的项目目录如下rag_refresher_notebook/ ├── data/ │ └── rag_intro.txt ├── rag_refresher.ipynb └── requirements.txt先手动创建一个data/rag_intro.txt内容可以用你自己关心的文档也可以先用下面的示例文本。我这里用一段非常短的 RAG 介绍举例实际使用时换成你自己的业务文档即可。RAG 全称是 Retrieval-Augmented Generation即检索增强生成。 它的核心思想是在大语言模型生成回答之前先从外部知识库中检索相关文本片段。 检索到的片段会和用户问题一起拼接到提示词中用于约束模型回答的内容和范围。 RAG 适合解决私有知识问答、企业制度问答、产品手册问答等场景。 与重新训练模型相比RAG 的优势是更新成本低、可解释性强、支持快速接入新知识。 传统 RAG 流程包括文档加载、文本切分、向量化、检索、重排和生成回答。 近年来RAG 还发展出了 Agentic RAG、Graph RAG、多模态 RAG 等进阶形态。4.2 初始化依赖与全局参数在 Notebook 的第一个单元格中导入依赖并设置全局参数。import os from pathlib import Path from langchain_community.document_loaders import TextLoader from langchain_text_splitters import RecursiveCharacterTextSplitter from langchain_huggingface import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 路径配置 BASE_DIR Path(.) DATA_PATH BASE_DIR / data / rag_intro.txt CHROMA_PATH BASE_DIR / chroma_db # 切分参数 CHUNK_SIZE 200 CHUNK_OVERLAP 30 # 检索参数 TOP_K 3 # Embedding 模型 EMBED_MODEL BAAI/bge-small-zh-v1.5 print(初始化完成)需要注意的是langchain_community在较新的langchain版本中已经被拆分出去所以这里单独安装。如果你使用的是旧版 LangChain导入路径可能是langchain.document_loaders请根据你的版本调整。4.3 文档加载与切分# 1. 加载文档 loader TextLoader(str(DATA_PATH), encodingutf-8) documents loader.load() print(f加载到 {len(documents)} 个文档对象) print(documents[0].page_content[:200])预期输出是文档前 200 个字。接着做切分# 2. 切分文本 text_splitter RecursiveCharacterTextSplitter( chunk_sizeCHUNK_SIZE, chunk_overlapCHUNK_OVERLAP, separators[\n\n, \n, 。, , , , ] ) chunks text_splitter.split_documents(documents) print(f切分出 {len(chunks)} 个文本块) print(示例块) print(chunks[0].page_content) print(---) print(chunks[1].page_content)这里每个 chunk 都是一个Document对象里面除了page_content还有metadata。我们可以给每个 chunk 加上序号方便后面追踪来源。4.4 构建向量库# 3. 初始化 Embedding 模型 embeddings HuggingFaceEmbeddings( model_nameEMBED_MODEL, encode_kwargs{normalize_embeddings: True} ) # 4. 生成向量库并持久化 vector_store Chroma.from_documents( documentschunks, embeddingembeddings, persist_directorystr(CHROMA_PATH) ) print(f向量库已创建位置{CHROMA_PATH})第一次运行需要下载 Embedding 模型时间取决于网络状况。下载完成后模型会缓存到本地后续运行不需要重复下载。如果你要重新构建向量库建议先删除已有的chroma_db目录避免旧数据和新增数据混在一起。4.5 检索召回与答案生成先做向量检索看看检索结果是否合理。query 什么叫检索增强生成 retriever vector_store.as_retriever(search_kwargs{k: TOP_K}) retrieved_docs retriever.invoke(query) print(f为问题检索到 {len(retrieved_docs)} 个相关块\n) for i, doc in enumerate(retrieved_docs): print(f--- 第 {i 1} 个结果 ---) print(doc.page_content) print()运行后你应该能看到和问题语义相关的几个片段。然后接入生成模型。这里以 OpenAI 兼容接口为例。如果你使用 OpenAI直接配置官方 Key如果你使用国内大模型服务通常也提供 OpenAI 兼容的 HTTP 接口。from langchain_openai import ChatOpenAI # 这里配置你的 API Key 和 Base URL # base_url 根据你的服务商调整 llm ChatOpenAI( modelgpt-4o-mini, temperature0.2, openai_api_keyos.getenv(OPENAI_API_KEY) ) def build_prompt(question: str, docs) - str: context \n\n.join([doc.page_content for doc in docs]) prompt f你是一个知识库问答助手。请仅根据以下资料回答问题。 如果资料中没有相关内容请直接回答“知识库中未找到相关信息”。 资料 {context} 问题{question} return prompt prompt build_prompt(query, retrieved_docs) answer llm.invoke(prompt) print(模型回答) print(answer.content)如果你的机器上没有大模型 API也可以先跳过生成环节只保留检索结果做人工判断。这一步不影响理解 RAG 前半段的工作。4.6 运行效果与结果说明整个 Notebook 运行完成后你的输出流程大致如下加载到 1 个文档对象 切分出 7 个文本块 向量库已创建位置chroma_db 为问题检索到 3 个相关块 --- 第 1 个结果 --- RAG 全称是 Retrieval-Augmented Generation即检索增强生成。 它的核心思想是在大语言模型生成回答之前先从外部知识库中检索相关文本片段。 ... 模型回答 检索增强生成RAG是一种在生成回答前先从外部知识库检索相关文本片段的技术。到这里一个最小的 RAG 链路就完全跑通了。5. RAG 知识库的指标与评估很多读者搭建完 RAG 后觉得“效果时好时坏”但说不清楚哪里差。这通常是因为缺少一套评估指标。5.1 检索层指标检索层关注的是“该搜出来的有没有搜出来”。常用指标如下指标解释直观理解RecallK前 K 条结果中命中的相关文档数 / 总相关文档数该有的结果召回了几成PrecisionK前 K 条结果中相关文档占比召回的 K 条里有几条靠谱Hit RateK前 K 条结果中是否至少有一个相关文档有没有命中一个关键答案MRR第一个相关结果排在第几位取倒数第一条有用结果出现得够不够早NDCGK考虑排序位置的折损累计增益排序顺序是否合理这里最关键的是 Recall 和 MRR。Recall 低说明知识库内容被切碎或索引错了MRR 低说明排序有问题需要通过重排改善。5.2 生成层指标检索准不等于答案好。生成层主要看三个指标忠实度Faithfulness生成内容是否严格依据检索到的资料没有编造。答案相关性Answer Relevance回答是否针对用户提出的问题。上下文相关性Context Relevance检索到的上下文是否足够支撑回答。这组指标可以从“主观感受”变成可量化的打分通常做法是让一个更强的模型作为裁判给生成结果逐项打分。5.3 如何理解这些指标在实际项目中不要只看一个指标。我建议按以下顺序排查先看 Hit Rate 和 Recall如果召回结果里压根没有正确答案生成再强也没用。再看 MRR 或 NDCG如果答案在召回结果里但排得很靠后会导致上下文过长、噪声过多。最后看忠实度如果检索没问题但答案还是编造问题出在 Prompt 或者说生成模型。你可以用这种思路给自己的知识库做一次“体检”。6. 如何让 RAG 更精准很多人问“如何创建精准的 RAG”。答案不是某一个技巧而是下面这几个环节的综合优化。6.1 从源头控制文档质量输入垃圾输出垃圾。RAG 也一样。去掉页眉页脚、水印、导航栏等无关文本。保留标题层级尽量使用 Markdown 或结构化数据。对 PDF 要人工抽样检查解析结果尤其是表格。对协议类文档比如 3GPP 规范或合同条款要按条款编号保留结构不要按固定字符数硬切。如果你发现某个文档检索效果总是差先检查原始解析结果大概率是解析阶段就丢了信息。6.2 混合检索与查询改写纯向量检索对关键词不敏感。比如用户搜“怎么请假”文档里写的是“休假流程”向量相似度可能不够高。改进思路有两种混合检索向量检索 关键词 BM25 检索再把结果合并排序。查询改写先让大模型把用户问题改写成更利于检索的形式例如补全同义词、扩展专业术语。例如用户问“工资什么时候发”可以改写成“公司工资发放日期、工资发放规则、发薪日”。6.3 Rerank 重排重排是提升 RAG 效果性价比最高的手段之一。先用向量库快速召回 20 条候选再用重排模型精排取前 3 条给大模型。这样可以兼顾召回率与精度。Rerank 的伪代码如下# 假设候选文档已经在 candidates 中 # reranker 是重排模型 scores reranker.score(question, candidates) top_results sort_by_score(candidates, scores)[:3]在 Notebook 中你可以先打印重排前后的对比直观感受重排模型的作用。6.4 Agentic RAG 与 Ontology RAG如果问题链比较复杂比如“对比这两个产品的售后政策”一次检索往往不够。这时可以引入 Agentic RAG让大模型像 agent 一样根据问题拆解检索计划多次检索直到信息足够。常见实现是 LangGraph 中的多步检索节点。另外还有 Ontology RAG它提前定义好概念之间的关系检索时不只找字面片段还会顺着知识图谱关系找到相关内容。适合做企业知识体系比较复杂、实体关系密集的场景。6.5 多模态 RAG 拓展如果你的知识库里有大量图片、表格、流程图纯文本 RAG 会丢失信息。多模态 RAG 的思路是使用多模态 Embedding 模型将图片和文本统一向量化。或者先用视觉模型把图片内容转录为文字再进入普通 RAG 流程。第二种方案实现成本更低目前也是很多项目的首选。7. 常见问题与排查思路在实际跑 RAG Notebook 时有几个高频问题经常被问到。7.1 Notebook 环境类问题问题现象常见原因解决思路Jupyter 无法启动conda 环境未激活先运行conda activate rag_notebook找不到已安装的包Kernel 使用的 Python 不是当前环境在 Notebook 中运行import sys; sys.executable检查代码能运行但报ModuleNotFoundError依赖装错了环境确认 pip install 和 Jupyter 在同一个环境云 Notebook 长时间无操作断开空闲会话被回收定期运行简单代码保活或分阶段保存结果检查 Kernel 环境是最关键的一步。很多人明明pip list能看到包但 Notebook 里导入失败几乎都是 Kernel 环境没切换对。7.2 中文切分与编码问题问题现象常见原因解决思路读取 TXT 出现乱码文件编码不是 UTF-8使用encodingutf-8必要时改为gbk切分后句子被拦腰切断分隔符未包含中文标点separators 中加入 “。”、“”、“”一个 chunk 全是空行原始文档有大量连续换行加载后先做文本清洗中文切分目前没有一个万能方案。我的建议是先按分隔符递归切再人工打印前 20 个 chunk 检查。如果发现大量句子被截断就应该调整chunk_overlap或分隔符。7.3 检索效果差问题现象常见原因解决思路检索结果和问题完全无关Embedding 模型不适合领域换成效果更好的中文 Embedding 模型相关结果排得太靠后向量检索不擅长精确关键词增加 BM25 混合检索或加 Rerank检索结果都是同一段文字切分太小导致重复片段调整 chunk_size增加多样性换成新文档后效果没变向量库没有重新构建删除 chroma_db 目录后重新生成这里要特别提醒每次修改切分参数或文档后记得清空向量库目录再重建。Chroma 持久化通常只会新增不会自动删除旧的分块容易造成“老的脏数据还在影响结果”的问题。8. 最佳实践与工程建议8.1 用模块化方式组织 Notebook一个 RAG Notebook 不要把所有代码堆在一个单元格里。建议按以下结构组织01 环境检查与依赖安装 02 文档加载与预览 03 文本清洗与切分 04 向量库构建 05 检索测试 06 重排测试 07 生成问答 08 效果评估这样当你调试时只需要重跑某个阶段的单元格不用从头再来。8.2 给 chunk 打上完整元数据在构建向量库时强烈建议保留每一条 chunk 的来源信息。示例代码for i, chunk in enumerate(chunks): chunk.metadata[chunk_id] i chunk.metadata[source] str(DATA_PATH)因为生产环境中用户最终需要知道“这个答案出自哪份文档的哪个部分”。如果元数据里只有文本内容后续审计和纠错都会很困难。8.3 从原型到生产环境的注意事项Notebook 跑通只是第一步。生产环境需要考虑更多内容权限控制不同角色只允许检索自己有权限的文档通常通过元数据过滤实现。银行、医疗等敏感行业尤其重要。文档更新机制新文档上线时只删除并重建对应文档的向量不要全库重建。监控与日志记录每个问题的命中文档、Rerank 分数、模型回答方便回溯分析。内容安全对检索内容和生成结果做合规过滤涉及敏感信息时要有脱敏和审计能力。评估闭环建立一套评估集每次修改切分策略、模型参数后都跑一遍评估用指标验证效果而不是凭感觉。8.4 合理选择 Embedding 模型与底座不要迷信“最大最贵的模型”。对中文知识库场景建议先在你的测试集上对比几个开源模型选效果好、推理速度快的。我见过不少项目问题不在模型而在文档解析。原始文档质量差换再大的模型也救不回来。9. 总结与下一步学习方向到这里这份RAG Refresher Notebook已经把 RAG 从文档加载到最终生成的完整链路走了一遍。你不仅看到了每个环节的代码还理解了为什么要切分、为什么要向量化、为什么要加重排、如何用指标评估效果。如果你想把 RAG 从“能跑”提升到“好用”下一步可以重点研究这几块混合检索与查询改写解决长尾问题。Rerank 模型引入提升排序质量。LangGraph 实现 Agentic RAG处理多步复杂问题。Graph RAG 或 Ontology RAG处理强关系型知识。评估数据集建设让每一项优化都可以量化。希望这份笔记本能在你搭建 RAG 知识库时少走一些弯路。如果中途遇到报错优先对照“常见问题与排查思路”一节大部分环境类问题都出在依赖版本和 Kernel 环境上。