当前位置: 首页 > news >正文

从零搭建本地 RAG 知识库:Ollama + LangChain 处理私有文档的完整实践

去年开始接触大模型应用开发时,第一个遇到的实际问题就是:怎么让模型回答我自己的文档内容?直接把文档塞进提示词里肯定不行——上下文窗口就那么大,几十页 PDF 根本放不下。后来才了解到 RAG(检索增强生成)是解决这个问题的标准做法。这里把从零搭建一个本地 RAG 系统的完整过程记录下来,所有组件都是开源的,数据不出本机。

一、什么时候需要自己搭 RAG?

  • 内部知识库问答:公司有几十份技术文档、产品手册,想让 AI 基于这些文档回答问题
  • 个人笔记检索:自己写了半年的工作笔记,想用自然语言找到相关内容
  • 敏感数据不外传:文档涉及客户信息或商业机密,不能上传到 ChatGPT 等云端服务
  • 定制化回答:想让模型基于特定的技术规范或行业标准来回答问题

RAG 的核心思路很简单:先把文档拆成小块(chunk)并向量化存起来,用户提问时把问题也向量化,去库里找到最相关的几块内容,连同问题一起丢给 LLM 去回答。模型不是"记住"了你的文档,而是每次都在你的文档里"查"到答案再回答。

二、方案一:本地部署 RAG(Ollama + LangChain + ChromaDB)

这是今天要搭建的方案,完全本地运行,数据不出机器。

环境准备

# 安装 Ollama(macOS / Linux) curl -fsSL https://ollama.com/install.sh | sh # Windows 去官网下载安装包 # 下载一个本地模型(7B 参数,普通电脑够用) ollama pull qwen2.5:7b # 安装 Python 依赖 pip install langchain langchain-community chromadb pypdf sentence-transformers

第一步:文档加载与分割

from langchain_community.document_loaders import PyPDFLoader from langchain.text_splitter import RecursiveCharacterTextSplitter from pathlib import Path def load_documents(doc_dir: str): """加载指定目录下的所有 PDF 文档""" docs = [] for pdf_file in Path(doc_dir).glob("*.pdf"): print(f"加载文档: {pdf_file.name}") loader = PyPDFLoader(str(pdf_file)) docs.extend(loader.load()) print(f"共加载 {len(docs)} 页") return docs def split_documents(docs, chunk_size=500, chunk_overlap=100): """将文档切分成小块,块之间保留重叠""" splitter = RecursiveCharacterTextSplitter( chunk_size=chunk_size, chunk_overlap=chunk_overlap, separators=["\n\n", "\n", "。", "!", "?", ",", " ", ""] ) chunks = splitter.split_documents(docs) print(f"分割为 {len(chunks)} 个文本块") return chunks # 使用示例 documents = load_documents("./my_docs") chunks = split_documents(documents)

chunk_sizechunk_overlap是两个需要调的核心参数。我试了不同组合:

chunk_sizechunk_overlap效果
20050块数多但上下文容易断,长段落被切碎
500100平衡,大部分场景适用
1000200块数少但一次性返回的信息量大

500/100 的组合对大多数技术文档表现比较平衡。

第二步:向量化存储

from langchain_community.embeddings import HuggingFaceEmbeddings from langchain_community.vectorstores import Chroma # 使用本地 embedding 模型(不需要联网) embeddings = HuggingFaceEmbeddings( model_name="BAAI/bge-small-zh-v1.5", model_kwargs={'device': 'cpu'}, encode_kwargs={'normalize_embeddings': True} ) def build_vector_store(chunks, persist_dir="./chroma_db"): """构建向量数据库并持久化到磁盘""" vector_store = Chroma.from_documents( documents=chunks, embedding=embeddings, persist_directory=persist_dir ) vector_store.persist() print(f"向量库已保存到 {persist_dir}") return vector_store vector_store = build_vector_store(chunks)

初次运行会下载 bge-small-zh-v1.5 模型到本地~/.cache/huggingface/目录,之后离线也能用。向量库保存在./chroma_db目录下,下次启动可以直接加载,不需要重新处理文档。

第三步:检索与问答

from langchain_ollama import OllamaLLM from langchain.chains import RetrievalQA from langchain.prompts import PromptTemplate # 连接 Ollama 本地模型 llm = OllamaLLM( model="qwen2.5:7b", temperature=0.3, num_predict=2048, ) # 创建检索器:每次返回最相关的 4 个文本块 retriever = vector_store.as_retriever(search_kwargs={"k": 4}) # 自定义提示模板 prompt_template = """你是一个技术文档助手,请基于以下文档内容回答问题。 文档内容: {context} 问题:{question} 请用中文回答,如果文档中没有相关信息,请直接说"文档中没有找到相关信息",不要编造答案。 """ prompt = PromptTemplate( template=prompt_template, input_variables=["context", "question"] ) # 组装 QA 链 qa_chain = RetrievalQA.from_chain_type( llm=llm, chain_type="stuff", retriever=retriever, return_source_documents=True, chain_type_kwargs={"prompt": prompt} ) # 开始问答 def ask(question: str): result = qa_chain.invoke({"query": question}) print(f"回答: {result['result']}") print(f"\n参考来源: {len(result['source_documents'])} 个文档片段") for i, doc in enumerate(result['source_documents'], 1): print(f" [{i}] {doc.metadata.get('source', '未知')} 第{doc.metadata.get('page', '?')}页") return result ask("我们的产品支持哪些导出格式?")

第四步:用 Gradio 搭个简单的 Web 界面

import gradio as gr def answer_question(question, history): result = qa_chain.invoke({"query": question}) sources = "\n".join([ f"- {doc.metadata.get('source', '?')} 第{doc.metadata.get('page', '?')}页" for doc in result['source_documents'] ]) return result['result'] + f"\n\n**参考来源:**\n{sources}" with gr.ChatInterface( answer_question, title="本地知识库助手", description="基于私有文档的问答系统,所有数据在本地运行", theme="soft", ) as demo: demo.launch(server_name="0.0.0.0", server_port=7860)

跑起来后浏览器打开 http://localhost:7860 就能用。我往里面塞了 5 份产品技术文档(共 120 页),问"如何配置数据备份"大概 3-4 秒出答案,比翻 PDF 快多了。

适合:有 Python 基础、需要处理私密文档、希望完全掌控数据流向的场景。

不太适合:不想折腾环境配置、只需要在线问答一次性的场景。

三、方案二:Dify 社区版——Web 界面可视化搭建

如果不想写代码,Dify 是一个开源(MIT 协议)的 LLM 应用开发平台,提供了可视化的 RAG 工作流编排界面。

# 使用 Docker 部署 git clone https://github.com/langgenius/dify.git cd dify/docker cp .env.example .env docker compose up -d

启动后访问 http://localhost:3000:

  1. 创建知识库 → 上传文档(支持 PDF、TXT、Markdown、网页抓取)
  2. 系统自动切分文档并向量化(内置 embedding 模型)
  3. 创建对话型应用 → 关联知识库 → 发布 API 或 Web 页面

Dify 的切分策略比手动调参友好——提供了"通用"和"父子切分"两种模式。父子切分是比较实用的功能:父块包含完整段落,子块是小片段用于精确检索,检索到子块后把父块内容送给 LLM,既命中准了又保留完整上下文。

Dify 还支持多用户权限管理,如果部门里几个人都要用同一个知识库,Dify 比脚本方案方便。

适合:不想写代码、需要 Web 管理界面、团队协作使用。

不太适合:需要深度定制检索逻辑、或者不想引入 Docker 依赖的场景。

四、方案三:使用 LangChain 自建 API 服务

如果想把 RAG 能力封装成 API 供其他系统调用,可以基于 FastAPI + LangChain 搭建推理服务:

from fastapi import FastAPI, HTTPException from pydantic import BaseModel from typing import List, Optional import uvicorn app = FastAPI(title="RAG API Service") class QueryRequest(BaseModel): question: str top_k: Optional[int] = 4 class SourceInfo(BaseModel): source: str page: int content_preview: str class QueryResponse(BaseModel): answer: str sources: List[SourceInfo] @app.on_event("startup") async def startup(): """启动时加载模型和向量库""" global qa_chain qa_chain = init_qa_chain() # 复用前文的初始化函数 @app.post("/ask", response_model=QueryResponse) async def ask_question(request: QueryRequest): if not request.question.strip(): raise HTTPException(status_code=400, detail="问题不能为空") result = qa_chain.invoke({"query": request.question}) sources = [] for doc in result['source_documents']: sources.append(SourceInfo( source=doc.metadata.get('source', '未知'), page=doc.metadata.get('page', 0), content_preview=doc.page_content[:100] )) return QueryResponse(answer=result['result'], sources=sources) if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8000)

API 跑起来后,其他系统可以这样调用:

curl -X POST http://localhost:8000/ask \ -H "Content-Type: application/json" \ -d '{"question": "如何安装和配置系统?", "top_k": 3}'

返回结果包含回答和引用来源,可以在前端展示带引用的答案。

适合:需要将 RAG 能力嵌入到现有系统、需要多人多系统调用的场景。

不太适合:只需要一次性的问答需求(方案一更直接)。

五、选型建议

维度Python 脚本方案Dify 社区版FastAPI 自建服务
代码量中等(约 100 行)零代码中等(约 200 行)
部署复杂度Python 环境即可需要 DockerPython + ASGI 服务器
Web 界面Gradio 可选自带完整 UI需自建前端
定制灵活度高(可改任意环节)中(受限于平台功能)高(全栈可控)
团队协作单机支持多用户需自建用户体系
文档更新需手动重建向量库支持增量更新需自实现更新逻辑
embedding 模型本地 BGE本地或云端本地 BGE

怎么选

  • 个人的技术文档需要做问答——Python 脚本方案,100 行代码搞定,想怎么调就怎么调
  • 团队内部要共享知识库,且不想写前端——Dify 社区版,开箱即用
  • 要把 RAG 能力嵌入已有的产品系统——FastAPI API 方案,封装成微服务供其他模块调用

六、几个实践中的坑

  1. chunk_size 不是越大越好:我一开始设了 2000,结果一个问题返回的内容填满了上下文,模型反而抓不住重点。
  2. 中文用 bge 系列 embedding:试过用英文 embedding 模型处理中文文档,检索准确率明显下降。
  3. source 信息要保留:刚开始没保存文档来源信息,模型回答的内容缺少可追溯性,不敢直接用。加了 source_documents 后效果好很多。
  4. 首次加载慢是正常的:bge 模型下载 + 文档向量化初次运行要几分钟,后续加载 Chroma 的持久化数据就快多了。

七、总结

RAG 是目前落地大模型应用最务实的方案——它不需要微调模型,不需要昂贵的 GPU 训练,只需要把文档处理好、向量库搭起来、检索策略调优,就能让模型基于你的数据回答问题。从 Python 脚本到可视化平台再到 API 服务,不同的复杂度对应不同的场景,自己评估一下需求和资源,选性价比最高的方式开始。搭完第一个 RAG 系统之后,你会发现"让 AI 理解我的文档"这件事没有想象的那么复杂。


本文涉及的组件(Ollama、LangChain、ChromaDB、Dify、Gradio)均为开源项目,可在各自官方仓库查阅最新文档。

http://www.jsqmd.com/news/1301714/

相关文章:

  • 无人便利店AI运维崩溃实录(内部故障日志首次公开):从边缘推理延迟到多模态感知漂移的8小时应急修复全流程
  • 【单片机毕业设计推荐】基于 STM32/51 单片机的气压温度采集与蓝牙报警监测系统设计 基于 STM32/51 单片机的环境温压监测及蓝牙终端控制系统设计(022404)
  • 第4篇:PyQt6 桌面应用中大模型 API 客户端的封装与费用控制
  • 微店商品详情API对接实战:权限自适应校验+私有商品兼容+结构化数据落地(Python全新方案)
  • 亦庄哪家宠物医院治疗髌骨脱位效果好
  • 如何免费解锁Switch第三方手柄支持:5个简单步骤实现全平台控制器兼容
  • 前端开发者必掌握的HTTP协议:从基础到实战排查指南
  • 免费开源桌面分区神器:3分钟打造整洁高效工作空间
  • 2026年海港区爱马仕包包回收指南:秦皇岛赵掌柜二奢实体店正规流程与行情解析(185-3117-2838) - 赵掌柜二奢
  • 苏州老房翻新转介绍率排行:老客愿意推荐给朋友的装修公司,致美的口碑积累 - 生活测评君
  • 新疆宣传片
  • 为什么我的 GraphRAG 上线就崩?权限、日志和流程才是真相
  • 猫抓浏览器扩展:3个真实场景告诉你为什么需要这个智能资源嗅探工具
  • 【单片机毕业设计推荐】基于 STM32 或 51 单片机的 TOF 激光测距与 WiFi 远程监测系统设计 基于 STM32 或 51 单片机的激光位移检测与声光报警装置开发(023404)
  • 2026 广德装修口碑榜单|深耕13年,广德智博装饰凭精工与诚信服务收获广德业主一致好评 - 推途云
  • PyTorch核心模块torch._C缺失错误:原因分析与彻底解决方案
  • 基于SpringBoot的短视频制作点播系统
  • 三轮车怎么托运最省心?2026年寄三轮车全流程避坑指南 - 快递物流资讯
  • 科研论文精读方法论:从速读到深度理解的技术路径
  • Python开发环境全解析:PyCharm、Conda、CUDA的区别与协同使用
  • 6款一键生成论文工具精选
  • 2026 Agent总结
  • 【单片机毕业设计推荐】基于 STM32/51 单片机的 NTC 温度测控与蓝牙通信系统设计 基于 STM32/51 单片机的智能温度加热降温控制系统设计(022704)
  • 【论文学习】MICCAI 2025 || Gut-VLM:面向大型视觉语言模型胃肠道图像分析的幻觉感知多模态基准
  • 第【95】期--时变信道与射频损伤情况下OFDM系统误码率性能分析--MATLAB完整代码
  • 文心一言长文写作实战突围:24小时极速交付SOP(含金融/法律/教育三大垂直领域定制框架)
  • 南孚传应的充电宝可以上高铁吗?符合铁路携带要求
  • 北京婚纱照:这 10 家口碑机构备婚新人都在选 - 江湖评测
  • 厂房瓦频繁生锈漏水?这款国产防腐隔热瓦,能用二三十年! - 林州鸿途网络
  • 数据驱动与鲁棒优化在电热综合能源系统中的应用