1. 为什么2026年还要自己搭本地知识库
这两年我帮不少朋友和中小团队搭过本地知识库,从最早的纯关键词检索,到后来接大语言模型做RAG问答,踩过的坑能写满一个笔记本。到了2026年,这件事的门槛其实比前两年低了很多——Ollama、LM Studio这类工具把模型部署做成了“下载即用”,Dify把整个RAG流程做成了可视化编排,pgvector让向量检索直接长在PostgreSQL里。但门槛低不代表没坑,反而因为工具太多,选型本身就是个坑。
先说清楚本地知识库到底解决什么问题。你手里有一堆PDF、Word、Markdown、Excel,甚至会议录音转出来的文本,想用自然语言问它问题,比如“去年Q3我们和供应商签的框架协议里,账期条款是怎么写的”,然后得到一个带出处的答案。这件事云端大模型能做,但数据要传出去,很多做制造、医疗、法律、财税的团队根本不敢传。本地知识库的核心价值就两条:数据不出内网,以及回答可溯源。
RAG(检索增强生成)是这套东西的技术骨架。简单说就是:把你的文档切块、向量化、存进向量库;用户提问时,先把问题也向量化,去库里找最相似的几块内容,再把这几块内容和问题一起塞给大语言模型,让它基于这些材料回答。这个流程听起来简单,但rag切块策略、embedding模型选择、检索召回率、多轮对话怎么设计,每一步都能决定最终效果是“能用”还是“没法用”。
这篇内容适合三类人:一是个人开发者,想在自己电脑上跑一个能问答的知识库;二是中小企业IT负责人,要给团队搭一套内部知识检索;三是已经用过云端方案、现在想迁到本地的技术同学。我会从选型讲到部署,从ollama本地部署讲到dify本地知识库搭建,把参数、配置、踩坑点都摊开说。你不需要是算法工程师,但得会基本的命令行操作,能看懂docker-compose文件。
提示:本文所有方案均基于本地或内网环境,不涉及任何数据外传。模型文件、向量库、应用服务全部跑在你自己的机器上。
2. 整体架构设计与工具选型逻辑
2.1 三层架构:模型层、检索层、应用层
搭本地知识库,我习惯把它拆成三层来看,这样选型的时候不会乱。
模型层负责两件事:一是把文本变成向量(embedding),二是根据检索到的内容生成回答(generation)。这两件事可以用同一个模型,也可以分开。我的建议是分开——embedding用专门的模型,比如bge-m3或者nomic-embed-text,体积小、速度快、效果好;生成用对话模型,比如Qwen2.5、Llama3.1、DeepSeek-R1的蒸馏版。Ollama和LM Studio都是模型层的运行工具,前者偏命令行和服务化,后者偏桌面交互和API调试。
检索层是向量数据库加检索逻辑。向量库选型很多,Milvus、Qdrant、Chroma、pgvector。我这两年最常用的是pgvector,原因很实在:你本来就要用PostgreSQL存业务数据,向量检索直接加个扩展就行,不用再维护一套独立服务。对于中小企业来说,少一个组件就少一个运维负担。Milvus适合向量规模上亿的场景,但大多数团队的知识库文档量在几万到几十万块之间,pgvector完全够用。
应用层是用户直接接触的部分,包括文档上传、切块、检索、对话界面。Dify是目前最省事的方案,它把RAG流程做成了工作流,支持可视化编排,还能接LangChain做更复杂的逻辑。如果你想要完全代码可控,那就用FastAPI + LangChain + LangGraph自己搭,灵活但工作量大。
2.2 Ollama还是LM Studio:别纠结,看场景
这两个工具经常被拿来比较,其实定位不太一样。
Ollama的优势在服务化和自动化。它跑在后台,提供统一的API端口(默认11434),支持模型拉取、运行、管理一条龙。你可以在命令行里ollama pull qwen2.5:7b,然后直接调API。它适合部署在服务器上,给整个团队用。缺点是国内下载模型有时候慢,需要配镜像源或者手动导入模型文件。
LM Studio的优势在桌面体验和模型实验。它有图形界面,能直观地看到模型加载、显存占用、推理速度,还内置了对话测试和API服务开关。2026年LM Studio升级到Bionic版本后,对本地API的管理更细了,可以设置端口、并发数、模型切换策略。它适合个人开发者在自己电脑上快速验证模型效果,或者做demo演示。
我的实际用法是:开发阶段用LM Studio试模型,确定用哪个之后,在生产环境用Ollama跑服务。两者不冲突,甚至可以同时跑,LM Studio占一个端口,Ollama占另一个。
2.3 Dify、LangChain、FastAPI怎么选
Dify适合快速上线。你不需要写太多代码,上传文档、配置切块参数、选模型、发布应用,一套流程在界面里完成。它内置了RAG引擎,支持多轮对话、引用溯源、API发布。对于中小企业来说,Dify能覆盖80%的需求。
LangChain + LangGraph适合需要复杂逻辑的场景。比如你要做agentic rag,让模型自己决定要不要检索、检索几次、用哪个工具查,那就需要LangGraph来编排状态机。再比如你要接多个数据源,或者做rag多轮对话的上下文管理,代码可控性就很重要。
FastAPI是胶水层,把模型服务、向量库、业务逻辑串起来,对外提供REST接口。如果你用Dify,它已经帮你做了这层;如果你自己搭,FastAPI是最顺手的选择。
注意:不要一上来就追求“全都要”。我见过太多团队一开始就上LangGraph做agentic rag,结果连基础的文档切块都没调好,检索召回率一塌糊涂。先把最简单的RAG跑通,再逐步加复杂度。
3. 核心细节解析与实操要点
3.1 文档切块:RAG效果的第一道分水岭
rag切块这件事,看起来简单,实际上决定了检索质量的上限。切得太碎,语义不完整,模型拿到半句话没法回答;切得太粗,一块里混了好几个主题,检索精度下降。
我常用的策略是按语义切块 + 重叠窗口。具体参数:块大小512到1024个token,重叠128到256个token。为什么要有重叠?因为文档里的句子经常跨段,比如一个条款的说明在上一段末尾,条件在下一段开头,没有重叠就会切断语义。
对于中文文档,还要注意标点。英文按句号、问号切就行,中文得考虑“。”“;”“”这些。LangChain的RecursiveCharacterTextSplitter支持自定义分隔符,我一般设成["\n\n", "\n", "。", ";", ",", " ", ""],优先按段落切,再按句子切。
表格和代码块要特殊处理。表格如果直接切,行和列的关系就丢了。我的做法是把表格转成Markdown格式,整块保留,不切。代码块同理,按函数或类切,不要从中间断开。
3.2 Embedding模型选择:别只看排行榜
Embedding模型负责把文本变成向量,它的质量直接决定检索能不能找到正确的内容。2026年常用的中文embedding模型有bge-m3、bge-large-zh、nomic-embed-text、text-embedding-3(这个得调API,本地用不了)。
我的选择逻辑是:先看维度,再看速度,最后看效果。bge-m3输出1024维,支持多语言,在中文检索任务上表现稳定,而且Ollama直接支持,ollama pull bge-m3就能用。nomic-embed-text输出768维,体积更小,速度快,适合文档量大的场景。bge-large-zh是中文专精,效果略好,但模型体积大一些。
有个坑要注意:embedding模型换了,向量库里的向量就得全部重算。因为不同模型的向量空间不兼容,你用A模型存的向量,用B模型查,结果全是乱的。所以选型的时候要慎重,一旦定了,后面换的成本很高。
3.3 向量库配置:pgvector的索引与参数
pgvector的安装很简单,PostgreSQL里执行CREATE EXTENSION vector;就行。但索引配置有讲究。
pgvector支持两种索引:IVFFlat和HNSW。IVFFlat建索引快,但查询精度依赖lists参数;HNSW查询快、精度高,但建索引慢、占内存。我的建议是:文档量小于10万块用HNSW,大于10万块用IVFFlat。
HNSW的关键参数是m和ef_construction。m控制每个节点的连接数,默认16,调到32能提升召回率但占更多内存。ef_construction控制建索引时的搜索范围,默认64,调到128效果更好但建索引更慢。查询时的ef_search参数也重要,默认40,调到100能提升召回率,但查询变慢。
-- 创建HNSW索引示例 CREATE INDEX ON documents USING hnsw (embedding vector_cosine_ops) WITH (m = 32, ef_construction = 128); -- 查询时设置ef_search SET hnsw.ef_search = 100;提示:索引不是越多越好。每个索引都会占内存和磁盘,而且写入时会变慢。先跑起来,根据实际查询效果再调。
3.4 检索策略:混合检索比纯向量更稳
纯向量检索有个问题:它对关键词不敏感。比如你问“ISO9001认证的到期时间”,向量检索可能找到一堆讲认证的文档,但漏掉具体日期。这时候需要混合检索——向量检索加关键词检索,再把结果融合。
我常用的做法是:向量检索取Top 20,关键词检索(用PostgreSQL的全文检索或者BM25)取Top 20,然后用RRF(Reciprocal Rank Fusion)融合,取Top 5给模型。RRF的公式很简单:score = sum(1 / (k + rank)),k一般取60。这样既能抓住语义相似,又能抓住关键词匹配。
Dify内置了混合检索的配置,你可以在界面里开启。如果自己用LangChain搭,可以用EnsembleRetriever把多个retriever组合起来。
4. 实操过程与核心环节实现
4.1 环境准备:从零到跑通Ollama
先装Ollama。Linux和macOS一条命令:
curl -fsSL https://ollama.com/install.sh | shWindows直接下安装包。装完之后验证:
ollama --version拉模型。国内下载慢的话,可以配镜像源,或者手动下载模型文件放到~/.ollama/models目录。我常用的是:
ollama pull qwen2.5:7b ollama pull bge-m3qwen2.5:7b做生成,bge-m3做embedding。7B模型在16GB内存的机器上能跑,如果有GPU更好,推理速度会快很多。跑起来之后,Ollama默认监听11434端口,你可以用curl测试:
curl http://localhost:11434/api/generate -d '{ "model": "qwen2.5:7b", "prompt": "你好", "stream": false }'4.2 用Dify搭建知识库:界面化流程
Dify的部署用docker-compose最省事。官方仓库里有docker/docker-compose.yaml,改一下环境变量就能跑。关键配置是数据库和向量库的连接信息。如果你用pgvector,就在Dify的设置里选PostgreSQL + pgvector,填上连接串。
部署完之后,进Dify后台,创建知识库,上传文档。Dify支持PDF、Word、Markdown、TXT等格式。上传后它会自动切块、向量化。切块参数可以在知识库设置里调,我一般把块大小设成800,重叠设成150。
然后创建应用,选“聊天助手”,在编排里把知识库挂上。Dify会自动做检索和生成。你可以在“引用和归属”里看到每句话的来源,点开能看到原文片段。这个功能对内部知识库特别重要,用户能验证答案是不是瞎编的。
4.3 用FastAPI + LangChain自建:代码可控方案
如果你要自己搭,核心代码分四块:文档加载、切块、向量化存储、检索生成。
from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_ollama import OllamaEmbeddings from langchain_postgres import PGVector from langchain_ollama import ChatOllama from langchain.chains import RetrievalQA # 1. 加载文档 loader = PyPDFLoader("docs/manual.pdf") docs = loader.load() # 2. 切块 splitter = RecursiveCharacterTextSplitter( chunk_size=800, chunk_overlap=150, separators=["\n\n", "\n", "。", ";", ",", " ", ""] ) chunks = splitter.split_documents(docs) # 3. 向量化存储 embeddings = OllamaEmbeddings(model="bge-m3") vectorstore = PGVector.from_documents( documents=chunks, embedding=embeddings, collection_name="knowledge_base", connection="postgresql+psycopg://user:pass@localhost:5432/dify" ) # 4. 检索生成 llm = ChatOllama(model="qwen2.5:7b", temperature=0.1) qa_chain = RetrievalQA.from_chain_type( llm=llm, retriever=vectorstore.as_retriever(search_kwargs={"k": 5}), return_source_documents=True ) result = qa_chain.invoke({"query": "账期条款是怎么写的?"}) print(result["result"]) print(result["source_documents"])这段代码跑通之后,外面套一层FastAPI就能对外提供服务。temperature设0.1是为了让回答更稳定,不要发挥太多。k=5是检索5块内容给模型,太多会超上下文,太少可能漏信息。
4.4 多轮对话设计:上下文怎么管
rag多轮对话的难点在于:用户的问题可能依赖上一轮的回答。比如先问“账期条款”,再问“那违约金呢”,第二个问题里的“那”指代的是上一轮的上下文。
我的做法是:把最近3轮对话的历史拼接到当前问题前面,一起做检索。但要注意,历史太长会稀释当前问题的权重。所以我会在检索时给当前问题更高的权重,或者用LLM先把历史压缩成一个独立的查询。
LangGraph里可以用状态机来管理:每个节点维护对话历史,检索节点根据历史生成查询,生成节点根据检索结果和历史生成回答。这样逻辑清晰,也方便调试。
5. 常见问题与排查技巧实录
5.1 模型下载慢、下载失败怎么办
ollama下载慢是最高频的问题。几个办法:一是配镜像源,在环境变量里设OLLAMA_HOST指向国内可访问的地址;二是手动下载模型文件,Ollama的模型存在~/.ollama/models/blobs目录,你可以从其他渠道拿到文件后放进去;三是用LM Studio下载,它的下载源有时候比Ollama快,下完之后再导入Ollama。
注意:手动放模型文件时,文件名和目录结构要对,否则Ollama识别不了。建议先用
ollama pull拉一个小模型,看看目录结构长什么样,再照着放。
5.2 检索结果不相关,怎么调
先看切块。如果块太大,一块里混了多个主题,检索就会不准。把块大小调小,比如从1024调到512,重叠从256调到128。再看embedding模型。bge-m3在中文上表现不错,但如果你的文档有很多专业术语,可能需要微调或者换模型。最后看检索参数。k值调大一点,比如从5调到10,看看召回的内容有没有改善。如果还不行,就上混合检索。
5.3 回答带幻觉,怎么压
幻觉就是模型编造原文里没有的内容。压制方法有几个:一是降低temperature,设到0.1甚至0;二是在prompt里明确要求“只根据提供的材料回答,材料里没有就说不知道”;三是检索时提高精度,确保给模型的材料是相关的;四是加引用溯源,让用户能看到答案来自哪块内容,模型知道要“对得上号”,编造的概率会降低。
5.4 显存不够、内存爆了怎么办
7B模型用4-bit量化后大概占4GB显存,16GB内存的机器能跑。如果显存不够,可以用CPU推理,但速度会慢很多。Ollama支持num_gpu参数控制用多少层跑在GPU上,剩下的跑CPU。LM Studio里可以直接看到显存占用,调整模型加载的层数。
如果文档量很大,向量库占内存也多。pgvector的HNSW索引会全部加载到内存,如果内存不够,就改用IVFFlat,或者把m和ef_construction调小。
5.5 常见问题速查表
| 问题 | 可能原因 | 排查方向 |
|---|---|---|
| 模型下载卡住 | 网络问题 | 配镜像源或手动导入 |
| 检索结果不相关 | 切块太大/embedding不合适 | 调小chunk_size,换embedding模型 |
| 回答编造内容 | temperature太高/检索不准 | 降temperature,加prompt约束 |
| 显存不足 | 模型太大/量化不够 | 用4-bit量化,调num_gpu |
| 多轮对话答非所问 | 历史上下文太长 | 压缩历史,或只保留最近2轮 |
| API端口冲突 | 多个服务占同一端口 | 改Ollama或LM Studio的端口配置 |
6. 个人实操心得与后续扩展方向
搭本地知识库这件事,我最大的体会是:别追求一步到位。我见过太多人一开始就想做agentic rag,结果连文档切块都没调好,检索召回率不到50%,最后项目不了了之。正确的做法是先跑通最简单的“上传文档-提问-回答”流程,然后根据实际效果逐步优化。
另一个心得是:embedding模型和切块策略比生成模型更重要。很多人花大量时间试哪个大模型回答得好,但其实如果检索阶段就没找到正确的内容,再好的生成模型也白搭。我一般会把70%的调优时间花在检索上,30%花在生成上。
后续扩展的话,有几个方向值得试。一是接视觉大语言模型,把图片、扫描件也纳入知识库,用OCR加多模态embedding处理。二是做ontology rag,给知识库加一层本体结构,让检索能理解实体之间的关系,而不只是文本相似。三是接MCP协议,让知识库能调用外部工具,比如查数据库、发邮件,从“问答”变成“办事”。
最后分享一个小技巧:如果你的文档更新频繁,不要每次都全量重算向量。用文档ID做增量更新,只处理新增和修改的文档。pgvector支持按ID删除和插入,LangChain的PGVector也有add_documents方法,配合文档哈希值判断是否变化,能省很多时间。