用 LangChain + Ollama 在本地跑一个 RAG 问答系统

# 用 LangChain + Ollama 在本地跑一个 RAG 问答系统

你有没有遇到过这种情况:电脑里存了几百份文档,想找某个具体信息却只能用 Ctrl+F 一个个搜?复制一大段给 AI,结果它根本不知道你在说什么。

这些问题可以用 RAG(检索增强生成)来解决。简单说就是让 AI 在回答问题前,先去你的文档里找相关信息,然后把找到的内容一起交给 AI 处理。这样 AI 就能基于你真正的知识库来回答,而不是胡编乱造。

今天来手把手教你搭建一个本地运行的 RAG 系统,全程只需要 Python 和 Ollama,不用任何云服务,不用付钱。

## 背景

大语言模型有两个天生的问题:一是知识截止日期,训练完成后它就不知道新东西了;二是它不知道你的私有数据,比如公司内部文档、个人笔记等。

传统解决办法是微调(Fine-tuning),但成本太高了——需要显卡、需要大量数据、需要重新训练。RAG 提供了另一个思路:不修改模型本身,让它在回答问题时先”查阅”你的文档。

2024 年之后,RAG 成了 AI 应用的标配。但大多数 RAG 方案都要依赖 OpenAI 的 API,要么收费,要么有隐私风险。这篇文章教你如何在本地跑一个完全私有的 RAG 系统。

## 目标

我们的目标是:构建一个本地问答系统,用户提问后,系统会:

1. 在指定的知识库(本地文件夹)中搜索相关内容
2. 把找到的相关内容整理成上下文
3. 把问题连同上下文一起交给本地运行的 LLM
4. 返回基于知识库的回答

技术要求:
– 全部在本地运行,不依赖外部 API
– 支持 PDF、TXT、Markdown 等常见文档格式
– 使用 Embedding 模型把文档转为向量
– 使用向量相似度搜索找到相关内容
– 使用 Ollama 运行 Qwen 等开源 LLM

## 步骤

### 1. 环境准备

确保安装了 Python 3.10+。然后创建项目目录并安装依赖:

“`bash
mkdir -p rag-demo && cd rag-demo
pip install langchain langchain-community langchain-ollama ollama pypdf python-dotenv
“`

langchain-ollama 是 LangChain 官方提供的 Ollama 集成模块,pypdf 用于读取 PDF 文件。

### 2. 下载运行 Ollama

根据你的操作系统,前往 https://ollama.com 下载并安装 Ollama。安装完成后运行:

“`bash
ollama pull qwen:7b
“`

这会下载 Qwen 7B 模型(大约 4GB,首次下载需要几分钟)。如果显卡够好,也可以选择 qwen:14b 或更大的模型。

然后在另一个终端启动 Ollama 服务:

“`bash
ollama serve
“`

### 3. 准备知识库

在项目根目录下创建 `docs` 文件夹,放入测试文档:

“`bash
mkdir docs
cat > docs/公司介绍.txt << EOF ## 公司介绍 创新科技成立于 2020 年,总部位于北京。 我们的核心产品包括: - AI 智能助手 - 企业知识管理平台 - 自动化办公系统 2023 年年度营收为 5000 万元人民币,团队规模 100 人。 EOF ``` 可以放入任意多的文档,支持 .txt、.md、.pdf 等格式。 ### 4. 编写 RAG 核心代码 创建 `rag_app.py`: ```python #!/usr/bin/env python3 """本地 RAG 问答系统 - 基于 LangChain + Ollama""" import os from pathlib import Path from langchain_community.document_loaders import TextLoader, PyPDFLoader, UnstructuredMarkdownLoader from langchain_community.embeddings import OllamaEmbeddings from langchain_community.vectorstores import Chroma from langchain_community.chat_models import ChatOllama from langchain.chains import create_retrieval_chain from langchain.chains.combine_documents import create_stuff_documents_chain # 配置参数 LLM_MODEL = "qwen:7b" EMBED_MODEL = "nomic-embed-text" CHROMA_PATH = "./chroma_db" DOCS_PATH = "./docs" def load_documents(doc_path: str): """加载指定目录下的所有文档""" documents = [] path = Path(doc_path) for file in path.rglob("*"): if file.is_file(): suffix = file.suffix.lower() try: if suffix == ".txt": loader = TextLoader(str(file), encoding="utf-8") elif suffix == ".md": loader = UnstructuredMarkdownLoader(str(file)) elif suffix == ".pdf": loader = PyPDFLoader(str(file)) else: continue docs = loader.load() for doc in docs: doc.metadata["source"] = str(file) documents.extend(docs) print(f"加载: {file.name}") except Exception as e: print(f"加载失败 {file.name}: {e}") return documents def create_vectorstore(documents): """创建向量数据库""" embeddings = OllamaEmbeddings( model=EMBED_MODEL, base_url="http://localhost:11434" ) if os.path.exists(CHROMA_PATH): print("加载已有向量数据库...") vectorstore = Chroma( persist_directory=CHROMA_PATH, embedding_function=embeddings ) else: print("创建新的向量数据库...") from langchain.text_splitter import RecursiveCharacterTextSplitter text_splitter = RecursiveCharacterTextSplitter( chunk_size=500, chunk_overlap=50 ) splits = text_splitter.split_documents(documents) vectorstore = Chroma.from_documents( documents=splits, embedding=embeddings, persist_directory=CHROMA_PATH ) return vectorstore def build_qa_chain(vectorstore): """构建问答链""" llm = ChatOllama( model=LLM_MODEL, base_url="http://localhost:11434", temperature=0.7 ) retriever = vectorstore.as_retriever( search_type="similarity", search_kwargs={"k": 3} ) combine_docs_chain = create_stuff_documents_chain(llm) qa_chain = create_retrieval_chain(retriever, combine_docs_chain) return qa_chain def main(): print("=" * 50) print("本地 RAG 知识问答系统") print("=" * 50) print("\n[1/4] 加载文档...") documents = load_documents(DOCS_PATH) print(f"共加载 {len(documents)} 个文档") print("\n[2/4] 构建向量数据库...") vectorstore = create_vectorstore(documents) print(f"向量数据库就绪") print("\n[3/4] 初始化问答系统...") qa_chain = build_qa_chain(vectorstore) print("问答系统就绪\n") print("-" * 50) print("你可以开始提问了(输入 quit 退出)") print("-" * 50) while True: question = input("\n问题: ").strip() if not question: continue if question.lower() in ["quit", "q", "exit"]: print("再见!") break result = qa_chain.invoke({"input": question}) print(f"\n回答: {result[\"answer\"]}") if "context" in result: print("\n参考来源:") for i, doc in enumerate(result["context"][:3], 1): source = doc.metadata.get("source", "未知") print(f" {i}. {source}") if __name__ == "__main__": main() ``` ### 5. 下载 Embedding 模型 首次运行前需要下载 Embedding 模型: ```bash ollama pull nomic-embed-text ``` 这是专门针对中文优化的 Embedding 模型,效果比原来更好。 ### 6. 运行系统 ```bash python rag_app.py ``` ## 运行结果 系统启动后显示: ``` ================================================== 本地 RAG 知识问答系统 ================================================== [1/4] 加载文档... 加载: 公司介绍.txt 共加载 1 个文档 [2/4] 构建向量数据库... 创建新的向量数据库... 向量数据库就绪 [3/4] 初始化问答系统... 问答系统就绪 -------------------------------------------------- 你可以开始提问了(输入 quit 退出) -------------------------------------------------- ``` 然后可以开始提问: ``` 问题: 公司的年营收是多少? 回答: 根据提供的信息,创新科技2023年的年度营收为5000万元人民币。 参考来源: 1. 公司介绍.txt ``` 再试一个问题: ``` 问题: 公司有多少人? 回答: 公司团队规模为100人。 参考来源: 1. 公司介绍.txt ``` 系统正确地从知识库中检索到了相关信息,并基于这些信息生成了回答。 ## 总结 这篇文章我们完成了: 1. **理解了 RAG 的原理**——让 AI 在回答前先检索你的文档 2. **搭建了完整的技术栈**——LangChain + Ollama + Chroma 向量数据库 3. **实现了核心功能**——文档加载、向量化存储、相似度搜索、问答生成 4. **提供了完整的代码**——可以直接运行的项目代码 整个系统完全在本地运行,不依赖任何外部 API。文档不会离开你的电脑,隐私有保障。 后续优化方向: - 使用更快的中文 Embedding 模型 - 增加增量更新知识库的功能 - 添加对话历史记录 - 支持更多文档格式 如果运行过程中遇到问题,评论区见。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇