从零搭建RAG私有知识库:LangChain+FAISS+Ollama实战指南

发布时间:2026/10/20 19:48:39
从零搭建RAG私有知识库:LangChain+FAISS+Ollama实战指南 1. 项目概述RAG私有知识库到底解决什么问题RAG检索增强生成这个缩写最近一年多几乎成了大模型落地场景里曝光率最高的词。但很多初次接触的朋友看到的资料要么是纯理论讲原理要么是上来就扔一堆代码中间的“为什么这么做”“资源不够怎么办”反而没人讲清楚。这篇就把整个过程拆开揉碎从一个完全空白的本地环境开始带你把一套私有知识库真正跑起来最终的效果是你把自己手头的文档丢进去然后像聊天一样问它“我们公司考勤制度里关于迟到是怎么规定的”它能从文档里找到答案并组织成一段通顺的话回复你。这套方案的核心价值在于数据私有化。你不需要把任何内部资料上传到公网服务所有环节——文档存储、向量检索、问题生成——都跑在本地机器上。很多团队第一次想尝试大模型应用卡住的原因往往不是技术本身而是“数据能不能出内网”这一关RAG私有化方案直接从源头规避了这个合规问题。对于只有一台普通电脑的初学者或者只有几台服务器的小团队这条路是完全走得通的。另外要说明的是这篇文章面向的是零基础读者但我会尽量把每一步的“为什么这么做”讲清楚而不是给一份只能照着敲的脚本。如果你已经跑通过类似项目可以直接跳到第4章看实操细节里面有不少我用过之后觉得值得记录的经验。2. 方案选型为什么是这四件套2.1 技术组件全景图先把整套系统的骨架画出来需要四个核心组件协同工作。文档加载与处理层负责把PDF、Word、TXT等格式的原始文件读出来并拆分成适合检索的文本块嵌入模型负责把文本块转换成一串高维向量让计算机能“理解”语义相似度向量数据库负责存储这些向量并支持快速检索大语言模型则负责阅读检索到的相关内容组织成自然语言答案。我选的组合是Python 3.10 LangChain框架 轻量级嵌入模型模型名以bge开头的小尺寸版本 FAISS向量库模型推理端用一个名为Ollama的本地模型管理工具。这套组合的特别之处在于它没有一个组件是重型的全部加起来在普通笔记本电脑上都能流畅运行不需要GPU服务器也不需要额外申请任何云服务。2.2 组件选型的思考过程这套选型是经过对比之后的折中方案。先看LangChain它最被诟病的地方是抽象层次多、升级频繁但它的文档加载器覆盖了市面上绝大多数常见格式对新手来说避免了到处找解析库的麻烦。如果你已经是熟练工完全可以不用框架自己写流程但对于从零起步的人LangChain能把开发工作量压缩到一个小时内。再看FAISS和向量数据库的取舍。市面上有Chroma、Milvus、Weaviate等专业向量数据库功能更丰富支持数据持久化、元数据过滤、分布式部署等。但对一个起步阶段的私有知识库来说FAISS的优势在于轻量和极快的检索速度它本质上是一个高效的相似度搜索库一个文件就搞定了索引的保存和加载。后续文档量增长到十万级以上再迁移到正式向量数据库也不迟。嵌入模型选择了国产的开源版本bge-small-zh它在中文语义匹配上的表现比同尺寸的通用模型要稳而且被LangChain直接支持调用。这里有个原则嵌入模型的语言能力必须和你的文档语言匹配英文文档用英文优化的模型效果才好中文场景强行用英文模型检索召回率会明显下降。2.3 模型推理端的本地化方案大语言模型这块选择Ollama来运行开源模型是因为它把模型下载、依赖管理、API服务三个环节都封装好了不需要手动配置Python环境和CUDA。下载一个几GB的模型文件执行一条命令就能起来一个兼容OpenAI格式的本地接口服务这对新手非常友好。Ollama支持的模型中我推荐从Qwen系列或者Llama 3系列的中小型版本起步。7B到8B参数量的模型经过量化后在有16GB内存的机器上可以流畅运行生成速度和连贯性都在可接受范围内。当然纯CPU运行会慢一些但测试问答场景完全够用。后面第5章会给出具体的效果实测记录。3. 环境准备先把地基打好3.1 安装Python与创建独立环境整个过程从安装Python开始。建议用3.10版本太新的版本有些底层依赖可能还没适配太老的版本则不支持LangChain的新特性。安装时务必勾选“Add Python to PATH”否则后面命令行里敲python会提示找不到命令。接下来手动创建虚拟环境——这一步很多教程会跳过去直接装依赖但强烈建议不要省。虚拟环境的作用是给这个项目隔离出一块独立的依赖空间不会和系统里其他Python项目互相污染后续想删掉整个项目直接删文件夹就行不会给系统留垃圾。创建方法在命令行里执行python -m venv rag_env然后激活环境。Windows系统激活命令是rag_env\Scripts\activatemacOS或Linux系统是source rag_env/bin/activate看到命令行前面出现了(rag_env)前缀就说明环境已经切过来了。之后安装的所有包都会装进这个隔离空间里。3.2 安装项目依赖依赖安装用pip一行命令搞定。需要说明的是LangChain最近做了模块拆分核心包和社区贡献的包需要分开装文档加载器、嵌入模型这些都在社区包里。我用的是下面这组经过实测的版本组合直接复制到命令行执行即可pip install langchain-core0.3.29 langchain0.3.13 langchain-community0.3.13 langchain-text-splitters0.3.5 faiss-cpu1.9.0 sentence-transformers3.4.1 ollama0.4.3 streamlit1.41.1 pypdf5.1.0这里有几个容易踩坑的细节。faiss-cpu在Windows上不需要额外装Visual Studio编译工具直接用预编译包就行pypdf是用来解析PDF文档的如果只处理TXT或Word可以暂时不装但建议装上备用streamlit是为了最后做一个可视化问答界面。安装过程中如果出现某个包找不到对应版本的情况大概率是Python版本不匹配检查一下是否用的3.10。还有一点安装完成后不要立刻关掉命令行后面还要验证安装结果。3.3 验证安装与准备测试文档装完后执行两条验证命令import langchain, faiss, sentence_transformers, ollama print(依赖检查通过)能打印出结果就说明环境没问题。如果某个import报错多半是该包没装成功重新执行一次pip install对应的包即可。接下来需要准备一份测试文档。不建议一上来就用长篇的PDF图书也不建议用图片版的扫描件没有文字层处理起来很麻烦最好是纯文本的TXT、Markdown或者内容清晰的PDF。我这次用的是某个模拟项目的一份产品FAQ文档大概四十多页内容涵盖功能介绍、故障排查、使用技巧。这类问答型文档对RAG系统非常友好因为问题和答案的表述方式与检索逻辑天然契合。3.4 Ollama模型部署最后安装Ollama推理引擎并下载模型。去官网下载对应操作系统的安装包装完打开服务然后执行ollama pull qwen2.5:7b这个命令会从模型仓库拉取一个约4.7GB的量化模型文件国内网络环境下可能需要等待一段时间建议使用稳定的网络连接。拉取完成后测试一下接口是否正常ollama run qwen2.5:7b输入任意问题能正常回复对话就说明本地大模型已经就绪。还没完需要额外拉一个嵌入模型——注意RAG系统的检索模块需要专用嵌入模型和大语言模型是两回事这个在后面会详细解释ollama pull bge-m3这个模型负责把文本变成向量文件体积比语言模型小很多下载很快。4. 分步构建RAG检索问答系统4.1 核心流程梳理整个系统的运行逻辑可以用一句话概括先对文档做索引再对问题做检索最后把检索到的内容交给大模型整理成答案。这里的关键是理解为什么需要“切分”这一步。如果你把一篇几万字的文档原封不动地丢给模型让它直接回答它会面临两个问题一是模型上下文窗口有限塞不下全部内容二是即便塞得下模型也难以从海量信息里精准定位某一句话回答容易模棱两可甚至张冠李戴。RAG的办法是把文档切成若干块——“块”的大小有讲究——然后对每个块做向量化存储用户提问时先把问题转成向量去库里找语义最接近的几个块再把这些块作为参考资料交给模型精读让它基于这些材料作答。类比来说这就像开卷考试先把整本教材切成小卡片并整理好索引考试时根据题目先翻出对应的几张卡片再照着卡片内容组织答案准确率自然比闭卷默写高得多。4.2 第一步加载与清洗文档创建一个Python脚本文件命名为build_knowledge_base.py下面是第一步代码from langchain_community.document_loaders import PyPDFLoader # 加载PDF文件 loader PyPDFLoader(data/product_faq.pdf) documents loader.load() print(f成功加载 {len(documents)} 页文档)不要以为加载就完事了这里有个非常重要的清洗步骤。PDF解析出来的文本通常混有页眉页脚、页码、无意义换行符这些噪声如果不处理会让切出来的文本块质量很差进而直接影响检索效果。我先用一段简单脚本做清洗def clean_text(text): # 去掉常见的页眉页脚和多余空白 lines [line.strip() for line in text.split(\n) if line.strip()] cleaned \n.join(lines) return cleaned documents [doc.page_content for doc in documents] # 得到干净的文本列表实操中你会发现有些PDF的排版会让两栏文本错乱拼接或者代码块里的空格被压缩掉这些问题必须在加载阶段发现并处理越早处理代价越小。如果文档格式五花八门建议统一转成TXT或Markdown再喂给系统兼容性最好。4.3 第二步文本切分的参数选择文本切分是整个RAG链路中对最终效果影响最大的环节没有之一。切分的块太大检索时可能把不相关的内容混进来块太小又可能切断语义完整的句子。LangChain提供了多种切分器最常用的是按字符数切分的RecursiveCharacterTextSplitter它优先尝试按段落分隔符切其次按句子再按字符层层递进尽量保持语义完整。我的配置如下from langchain_text_splitters import RecursiveCharacterTextSplitter text_splitter RecursiveCharacterTextSplitter( chunk_size300, chunk_overlap50, separators[\n\n, \n, 。, , , , , , ] ) chunks text_splitter.split_text(\n\n.join(documents)) print(f切分为 {len(chunks)} 个文本块)两个关键参数chunk_size是每个块的目标字符数chunk_overlap是相邻块之间重叠的字符数。重叠的目的是保证跨越切分边界的语义片段在两侧都各保留一份避免“苹果公司的总部位于……”这句话正好被拦腰截断导致两半都语义残缺。为什么选300这个数值这是针对中文场景的经验值。中文信息密度高300字大约能表达一个完整的知识要点同时又能被模型的上下文窗口轻松容纳。如果文档是技术规范类的可以适当调大到500如果是社交媒体类碎片文本200就行。这个参数没有绝对标准后面讲评测时会给出调试方法。4.4 第三步向量化并构建索引现在进入重头戏。文本块准备好了但要让计算机能“检索”必须把它们变成向量。这里用到的是bge-m3嵌入模型它会把一段文本映射成一个高维空间中的坐标点语义相近的文本在坐标空间里距离也近。from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import FAISS from langchain_ollama import OllamaEmbeddings # 使用本地Ollama提供的bge-m3模型生成向量 embeddings OllamaEmbeddings(modelbge-m3) # 为每个文本块生成向量并创建FAISS索引 vectorstore FAISS.from_texts( textschunks, embeddingembeddings ) # 保存索引到本地文件 vectorstore.save_local(faiss_index) print(向量索引构建完成已保存到 faiss_index 目录)这一步会消耗一些内存和CPU时间。我实测下来一万个文本块的向量化大约需要三到五分钟做完后能看到faiss_index文件夹里的索引文件。向量化完成前不用担心磁盘占用几十MB而已。有人会问为什么不用前面提到的sentence-transformers库直接加载模型而要走Ollama两种方式都能工作但走Ollama可以复用已经拉取好的模型省去重复下载而且接口统一管理起来更清爽。4.5 第四步加载索引实现检索问答建立索引是一次性的工作但用户的提问是多次的。所以需要一个单独的问答脚本每次启动时加载已有的FAISS索引然后接收用户问题、检索相关文本块、调用大模型生成答案。这里先把检索逻辑写清楚from langchain_community.vectorstores import FAISS from langchain_ollama import OllamaEmbeddings from langchain_ollama import OllamaLLM # 加载已有的索引 embeddings OllamaEmbeddings(modelbge-m3) vectorstore FAISS.load_local(faiss_index, embeddings, allow_dangerous_deserializationTrue) # 用户问题 query 产品无法连接Wi-Fi时应该怎么办 # 检索最相关的4个文本块 docs vectorstore.similarity_search_with_score(query, k4) for i, (doc, score) in enumerate(docs): print(f候选片段 {i1}相似度{score:.4f}) print(doc.page_content) print(---)这里similarity_search_with_score会返回检索结果和相似度分数分数越小代表距离越近可以借此观察检索质量。建议先单独跑一下这一步看看检索到的文本块与问题是否真正相关。如果结果明显跑偏先别急着调大模型排查切分参数或嵌入模型这一步对了整个系统才对。4.6 第五步用LangChain串联完整问答流程最后一步把检索和生成拼接起来形成完整的RAG问答链路from langchain.chains import RetrievalQA from langchain_ollama import OllamaLLM # 设置本地大语言模型 llm OllamaLLM(modelqwen2.5:7b) # 创建检索增强问答链 qa_chain RetrievalQA.from_chain_type( llmllm, retrievervectorstore.as_retriever(search_kwargs{k: 4}), return_source_documentsTrue ) # 执行问答 question 产品无法连接Wi-Fi时应该怎么办 result qa_chain.invoke({query: question}) print(回答) print(result[result])参数k控制了检索返回的文本块数量我一般设置在3到5之间。太少了信息可能不全太多了噪声会让模型抓不住重点4是一个均衡值。运行这条脚本后如果一切正常你应该能看到模型基于检索到的片段生成的答案底下还会跟着来源片段方便核对答案是否出自原文。4.7 编写可复用脚本的注意事项如果只是跑通一次demo把上面代码粘到脚本里顺序执行就够了。但后续如果要添加新文档、重新构建索引、持续问答代码结构最好是函数化的。我一般会拆成三个文件ingest.py负责加载文档和构建索引query.py负责加载索引和问答utils.py放公共的文本清洗函数。这样新增文档时只需增量执行ingest.py索引文件会更新问答端不用动。还有一个小细节FAISS索引文件的加载接口出于安全考虑要求传入allow_dangerous_deserializationTrue这是新版的安全机制防止加载来路不明的序列化数据。如果是从自己机器上生成的索引可以放心打开如果是从网上下载的别人分享的索引文件建议重新验证一下内容再使用。5. 可视化界面用Streamlit做一个聊天机器人命令行跑通之后体验上总觉得差点意思。为了让自己用起来舒服也为了让非技术背景的同事能直接上手我基于Streamlit写了一个简单的网页问答界面。它创建了浏览器中的对话入口输入问题、点击按钮就能得到答案不需要接触任何代码。import streamlit as st from langchain_community.vectorstores import FAISS from langchain_ollama import OllamaEmbeddings, OllamaLLM from langchain.chains import RetrievalQA st.set_page_config(page_title本地知识库问答, page_icon) st.title(本地知识库问答) # 缓存加载避免每次操作重复加载模型 st.cache_resource def load_chain(): embeddings OllamaEmbeddings(modelbge-m3) vectorstore FAISS.load_local(faiss_index, embeddings, allow_dangerous_deserializationTrue) llm OllamaLLM(modelqwen2.5:7b) return RetrievalQA.from_chain_type( llmllm, retrievervectorstore.as_retriever(search_kwargs{k: 4}), return_source_documentsTrue ) qa_chain load_chain() # 聊天界面 if messages not in st.session_state: st.session_state.messages [] for message in st.session_state.messages: st.chat_message(message[role]).write(message[content]) if prompt : st.chat_input(请输入你的问题): st.chat_message(user).write(prompt) st.session_state.messages.append({role: user, content: prompt}) with st.spinner(正在检索资料并生成回答...): response qa_chain.invoke({query: prompt}) answer response[result] st.chat_message(assistant).write(answer) st.session_state.messages.append({role: assistant, content: answer})启动方式是在命令行执行streamlit run app.py浏览器会自动打开一个本地页面。实测中这个页面在局域网内也可以访问同一网络的同事可以直接在手机上体验简单来说就是一个小型团队知识库问答应用。这里最实用的设计是用了st.cache_resource装饰器。如果没有它页面每次交互都会重新加载嵌入模型和向量索引等待时间动辄十几秒体验会很糟糕。加上缓存后模型只加载一次后续问答秒级响应。6. 效果优化与实测怎么把“模糊的实现”变成“好用的系统”6.1 第一次实测的预期与现实完成上述搭建后我拿那份模拟项目的产品FAQ做了一轮完整测试记录了几个典型问题的实际表现作为衡量系统基准效果的参考。第一个问题“产品支持哪些连接方式”系统回答准确列出了蓝牙、Wi-Fi、USB三种方式并说明了各自的适用场景回答内容和原文基本一致。第二个问题“电池续航时间多久”答案给出的续航数据和文中的“正常使用约8小时、重度使用约5小时”完全吻合而且正确区分了两种使用场景。第三个问题“设备进水了怎么办”这次的回答开始暴露出问题——模型给出的建议里有“立即开机检查”这种危险操作而原文明确写的是“切勿开机先擦干静置24小时”。为什么会出现这种错误核心原因是检索环节没有找到足够精准的文本片段模型只能凭自己训练时习得的常识作答。这恰好说明RAG系统质量的关键在检准率而不在生成模型本身。6.2 针对检索效果的调优方法针对上面的问题我按优先级从低到高逐步做了四项调整每一项都实实在在地提升了准确率。第一步是调整文本切分参数。把chunk_size从300改成200chunk_overlap从50改成30意图是让每个文本块包含更聚焦的语义单元检索时命中的候选片段更精准。修改后重新构建索引该问题候选片段相关性有所提升。第二步是扩充相似度检索数量。把k值从4提高到8先让系统多召回几个候选片段再用后续步骤做精排防止正确答案被截断在结果窗口之外。但这同时带来了副作用模型看到的噪声增多了有时候反而被干扰。第三步是引入重排序模型。这是效果提升最明显的一步先用向量检索召回10个候选再用一个专门的中文重排序模型逐一对候选与问题的相关性打分只取分数最高的3个交给大模型。重排序模型理解语义的能力比纯向量距离更细腻能有效过滤掉“字面相关但语义跑偏”的文本。这也是对第二步副作用的修正方案。第四步是提示词优化。在检索QA链中自定义了一个提示模板from langchain.prompts import PromptTemplate custom_prompt PromptTemplate( template( 你是一个严谨的知识库问答助手。请根据以下资料片段回答问题。 如果资料中没有相关信息请直接回答“资料库中未找到相关信息” 不要编造。\n\n 资料片段\n{context}\n\n 问题{question}\n\n 回答 ), input_variables[context, question] ) qa_chain RetrievalQA.from_chain_type( llmllm, retrievervectorstore.as_retriever(search_kwargs{k: 4}), chain_type_kwargs{prompt: custom_prompt} )这段提示词的核心作用是把模型“不懂装懂”的空间压缩到最小。模型基于资料回答问题时即使没找到完整信息也倾向于靠常识补全这在严谨的知识管理场景里是致命的而明确告诉它“没找到就直说”能堵住大部分编造来源。经过四步调整后前面提到的“设备进水”问题得到了正确回答。需要提醒的是每调整一个参数都应该在固定的测试问题上重新跑一遍记录回答正确与否而不是凭感觉判断。建议准备一份涵盖不同难度的问题集至少二十条当作回归测试用例每次改动后逐条验证。6.3 在普通电脑上的资源占用与性能表现很多人会担心本地部署跑不动模型。我实际测试了这台模拟用的电脑配置CPU是普通桌面级处理器内存16GB没有独立显卡。在这种配置下模型回答一个问题的生成时间大约在五到十秒之间这个速度在对话场景下可以接受。内存占用方面需要注意同时加载嵌入模型和大语言模型峰值占用约8GB再加上系统本身的占用16GB内存刚好够用但浏览器标签开多了可能会感觉到卡顿。如果机器只有8GB内存建议换用更小的语言模型版本比如Qwen系列更轻量的量化档位生成速度会变快回答质量略有下降但日常问答影响不大。如果追求更好体验有一块显存8GB以上的独立显卡模型可以完全放进显存生成时间能缩短到两秒以内。6.4 继续扩展的三个方向这套骨架跑通后可以根据实际需求做不少扩展。第一是增加文档类型支持目前只实现了PDF和TXT接上Word文档加载器和HTML加载器后就能处理常见办公文档。第二是增加增量更新机制目前文档一多就要全量重建索引对于频繁更新的动态文档库效率偏低一种常用方案是定期重建另一重方案是把新文本块直接追加到现有索引。第三是增加多轮对话记忆目前每次提问都是独立的不记得之前的对话配合历史记录组件后体验会更自然。有意思的是这三个方向都不需要推翻现有架构都是在LangChain既有组件上做配置调整。这也是当初选择这套技术栈的回报生态足够丰富后续成长空间留得很足。实测下来整个项目从零开始到跑通大约需要三到四个小时大部分时间花在工具安装和模型下载上真正写代码的时间不到四十分钟。建议第一次动手时保持耐心如果某个环节失败了优先检查环境配置和网络状态这两个原因占了初学者遇到问题的八成以上。等系统真正跑起来你会对RAG这个技术在知识管理场景里的价值有非常直观的体会——它并不神秘但确实能成为解决实际问题的趁手工具。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询