RAG技术与应用(二) 📅 2026/7/17 21:45:08 基于 DeepSeek FAISS 的本地知识库检索实践在上一篇里我们先把 RAG 的概念、流程和 embedding 选型理清楚了。这一篇不再停留在概念层而是结合当前目录下的一个实际项目完整拆解一个本地知识库问答系统是怎么搭起来的。这个项目的技术路线可以概括为文档来源PDF文档处理PyPDF2文本切分RecursiveCharacterTextSplitter向量化DashScopeEmbeddings向量检索FAISS大模型生成DeepSeek-V3 via Tongyi结果展示答案 来源页码这是一条非常典型的 RAG 工程链路。它不追求花哨而是强调可落地、可维护、可溯源。一、为什么要做本地知识库检索大模型本身很强但它有两个天然限制参数里的知识不是实时更新的它不天然知道你的私有文档、内部资料和业务规则对于企业文档、白皮书、制度文件、产品手册这类场景最常见的需求不是“让模型瞎聊”而是给用户一个准确答案答案最好能指出来源文档更新后系统能快速同步不希望每次改知识都重新训练模型所以本地知识库检索的价值就在这里把“知识”从模型参数里拆出来变成一个可更新、可检索、可追溯的数据层。二、这个项目的整体架构你当前这个项目的主流程可以拆成三步Step 1文档预处理PDF 文件会先被读取然后拆成文本再按规则切成多个 chunk。在这个过程中还会记录每个文本块对应的页码方便后面返回来源。Step 2知识库构建切分后的文本块会被 embedding 模型转换成向量再写入 FAISS 向量库。同时保存页码映射信息确保检索结果可以追溯到原文页码。Step 3问答生成用户提出问题后系统先用向量检索找出最相关的文本块再把这些文本块拼接成上下文交给 DeepSeek-V3 生成最终回答。这套逻辑其实就是用户问题 - 语义检索 - 上下文拼接 - LLM 生成 - 答案输出三、技术栈选择这个项目的技术选型比较清晰也很适合做本地知识库问答的入门案例。1. PyPDF2用于从 PDF 中提取文本。它的优点是接口简单适合快速做 PDF 文本抽取对单纯文本型 PDF 足够实用它的局限也很明显对扫描版 PDF 支持有限对复杂排版的提取效果不一定稳定所以它更适合“文本型 PDF”或者“先做原型验证”的场景。2. RecursiveCharacterTextSplitter用于长文本切分。在你的项目里切分参数是chunk_size1000chunk_overlap200这个设置比较合理属于 RAG 里常见的工程配置chunk_size保证上下文足够chunk_overlap保证跨段落语义不断裂同时它支持递归切分会优先按更自然的边界去拆段落换行句号空格字符级兜底这能尽量减少把一句完整意思切碎的概率。3. DashScopeEmbeddings用于把文本块转成向量。你这里用的是text-embedding-v1这属于比较典型的云端 embedding 方案。它的好处是上手快省去本地部署 embedding 模型的成本适合快速搭建 demo 或课程案例4. FAISSFAISS 是这个项目里的向量检索核心。它的作用是把文本向量高效存起来并支持相似度搜索。对于本地知识库来说FAISS 很常见原因很直接检索速度快部署简单工程成熟本地可持久化5. DeepSeek-V3 / Tongyi生成部分使用的是Tongyi(model_namedeepseek-v3)。也就是说这个项目不是“检索完直接返回”而是把检索到的内容交给大模型去组织语言、提炼答案。这样做的意义是检索负责找资料LLM 负责组织答案两者分工明确整体效果会比直接让模型凭记忆回答稳得多。四、文档预处理从 PDF 到可检索文本这部分是整个项目最重要的基础。1. 逐页提取文本在extract_text_with_page_numbers()里代码会遍历 PDF 的每一页提取该页文本将文本追加到总文本中同时记录每个字符来自哪一页这个“字符到页码”的映射非常重要。因为后面文本会被切成 chunk而 chunk 并不知道原始来源页。如果不提前记录映射后面就很难做“引用来源页码”的展示。2. 长文本切分在process_text_with_splitter()中文本被切分成多个 chunk。你的实现中采用了递归字符分割器并指定了优先级\n\n\n.空格字符级兜底这意味着它会尽量优先在自然边界处分割避免硬切。3. 页码映射切块后代码通过字符位置统计每个 chunk 主要来自哪一页。具体做法是计算 chunk 在原文中的字符区间找出这个区间覆盖了哪些页码统计出现次数最多的页码作为该 chunk 的主来源页这一步实现了一个很实用的功能检索回答不仅给答案还能告诉用户答案来自 PDF 的哪一页对于知识问答系统来说这一点非常加分。因为用户往往不只想知道“结果是什么”还想知道“依据在哪里”。五、知识库构建文本块到向量库文档切分完成后接下来就是把文本块变成向量并写入 FAISS。1. Embedding 编码每个 chunk 会通过DashScopeEmbeddings变成向量表示。这些向量会尽量把“语义相近”的内容放得更近。这一步的本质是文本 - 向量这也是 RAG 的关键基础之一。如果 embedding 不好后面的检索精度会很差。2. FAISS 索引构建代码通过FAISS.from_texts(chunks, embeddings)直接把文本块和向量库建立起来。这样系统就具备了相似度检索持久化保存后续快速加载3. 持久化保存你的项目里保存了三类内容index.faissFAISS 索引文件index.pkl元数据page_info.pklchunk 到页码的映射这三个文件共同保证了系统可以“构建一次多次复用”。这也是本地知识库项目和临时脚本的区别。真正可用的 RAG 系统必须考虑持久化。六、问答阶段检索增强生成是怎么发生的当用户输入一个问题后流程会进入问答阶段。1. 问题向量化用户 query 先经过 embedding 转换成向量。这个向量会用来和知识库中的 chunk 向量做相似度比较。2. Top-K 召回代码使用docs knowledge_base.similarity_search(query, k10)这一步会返回最相关的 10 个文本块。这就是 RAG 里的Retrieval。3. 上下文拼接检索结果会被拼接成一段上下文context \n\n.join([doc.page_content for doc in docs])然后和用户问题一起构造 prompt。这一步就是Augmentation。也就是把检索到的文档片段增强进生成上下文。4. 大模型生成答案最后将 prompt 传给 DeepSeek-V3由模型生成最终回答。这一步对应Generation。也就是说检索负责“找资料”生成负责“说人话”如果检索到的内容足够准最终答案一般会更稳定、更贴近原文。七、页码溯源本地知识库项目里非常实用的一环你这个项目里比较好的一个点是保留了页码信息。在问答输出时除了答案还会打印来源页码文本块页码: x这个功能看起来简单但实际很重要。因为知识库问答不是普通聊天它常常需要回答这个结论来自哪一页原文依据是什么用户能不能快速回到文档查看上下文这也是很多企业场景里 RAG 比纯聊天更有价值的原因之一。不是因为它“更会说”而是因为它“可验证”。八、这套实现的优点这个项目虽然不复杂但工程思路是正确的优点很明确1. 结构清晰整个流程分成了文档抽取切块向量化检索生成很适合作为 RAG 入门案例。2. 便于扩展后续你可以很自然地在这个框架上继续加rerankchunk 优化多轮对话记忆问题改写混合检索引用片段高亮3. 可追溯保留页码信息让答案更容易被用户验证。4. 可替换当前项目里用的是 DashScopeEmbeddings 和 DeepSeek-V3。但这两个位置都可以替换embedding 可以换成本地模型LLM 可以换成其他对话模型向量库也可以换成 Milvus、Chroma、Qdrant 等九、这套实现的局限如果从更严格的工程角度看这个版本也有一些典型局限。1. 只做了基础相似度检索目前是similarity_search还没有引入 rerank。当候选 chunk 很多时单纯向量相似度未必总能选到最优结果。2. 切块策略还可以更精细现在使用的是通用字符切分。如果是结构更复杂的 PDF后续可以考虑按标题切块按段落层级切块保留表格和列表结构3. 未做混合检索当前依赖纯向量检索。对于包含大量专有名词、条款编号、章节号的文档混合检索通常会更稳。4. 没有显式答案约束现在 prompt 主要是“根据上下文回答问题”。如果要更严谨可以继续加上不允许编造找不到就说不知道尽量引用原文表达十、小结这个 DeepSeek FAISS 的本地知识库项目实际上很好地展示了一个完整 RAG 系统的工程闭环用 PyPDF2 提取 PDF 文本用递归切分器构造语义块用 DashScopeEmbeddings 把文本转成向量用 FAISS 做高效检索和持久化用 DeepSeek-V3 生成最终答案用页码映射实现结果溯源它不是一个“炫技型”系统但它非常适合作为真实可用的本地 RAG 原型。而这恰恰是 RAG 的核心价值所在不是让模型凭空变聪明而是让模型在你的知识上变得可靠。