使用 LangChain 构建本地 RAG 系统:让大模型阅读你的私有文档

背景

你肯定遇到过这种情况:电脑里存了几百篇笔记,想让 AI 基于这些内容回答问题。普通的 ChatGPT 再强,也看不到你本地的东西——它的训练数据总有截止日期,而且不会读你的文件。

这就是 RAG 要解决的问题。RAG 全称 Retrieval Augmented Generation(检索增强生成),核心思路是:先从你的文档里找到相关内容,再让大模型根据这些内容回答。这样答案有据可查,不会瞎编;也能用到最新的、私有的信息。

这篇文章教你用 Python、LangChain 和 Ollama 在本地搭建一个完整的 RAG 系统。全部开源,不用任何付费 API。

问题

假设你的笔记文件夹长这样:

notes/
├── 2024-01-15-关于Go语言的思考.md
├── 2024-03-20-Python异步编程心得.md
├── 2024-06-10-分布式系统设计原则.md
└── 2024-11-05-如何优化数据库查询.md

现在你想问:”我之前有没有记录过缓存的设计模式?”

常规做法是把所有笔记复制粘贴到对话框。先不说麻烦,文件太多根本塞不进去。

RAG 就是来处理这个问题的:自动理解问题,找到最相关的文档块,让大模型基于这些内容生成答案。

详细步骤

准备工作

首先,我们需要安装必要的 Python 包。创建一个新的 Python 环境(建议使用 Python 3.10 或更高版本),然后执行以下命令:

pip install langchain langchain-community langchain-text-splitters chromadb ollama pypdf sentence-transformers

这些包分别是:

  • langchain:RAG 框架
  • langchain-text-splitters:分割文档
  • chromadb:向量数据库,存文档嵌入
  • ollama:本地跑大模型
  • sentence-transformers:生成文本向量

步骤一:启动本地大模型

还没装 Ollama?从官网下载安装。装好后在终端运行:

ollama serve
ollama pull llama3

这会下载 Llama 3(约 4.7GB)。如果网络不好,可以选 mistralphi3 这类更小的模型。

步骤二:准备文档加载器

创建一个 loader.py,实现文档加载。LangChain 支持 Markdown、PDF、TXT、Word 等多种格式:

from langchain_community.document_loaders import DirectoryLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.schema import Document
import os

def load_documents(folder_path: str):
    """加载指定文件夹下的所有文档"""
    
    # 支持多种格式的加载器
    loaders = {
        '.md': TextLoader,
        '.txt': TextLoader,
        '.py': TextLoader,
    }
    
    all_documents = []
    
    for root, dirs, files in os.walk(folder_path):
        for file in files:
            file_path = os.path.join(root, file)
            ext = os.path.splitext(file)[1].lower()
            
            if ext in loaders:
                try:
                    loader = loaders[ext](file_path, encoding='utf-8')
                    docs = loader.load()
                    all_documents.extend(docs)
                    print(f"加载成功: {file_path}")
                except Exception as e:
                    print(f"加载失败 {file_path}: {e}")
    
    return all_documents

def split_documents(documents: list[Document], chunk_size: int = 500, chunk_overlap: int = 50):
    """将文档分割成小块"""
    
    text_splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,
        chunk_overlap=chunk_overlap,
        length_function=len,
        separators=["\n\n", "\n", "。", " ", ""]
    )
    
    return text_splitter.split_documents(documents)

步骤三:创建向量存储

文档需要转换成向量才能进行相似度搜索。我们使用 ChromaDB 作为向量数据库:

from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import HuggingFaceEmbeddings

def create_vector_store(documents, persist_directory: str = "./chroma_db"):
    """创建向量存储"""
    
    # 使用轻量级嵌入模型
    embeddings = HuggingFaceEmbeddings(
        model_name="sentence-transformers/all-MiniLM-L6-v2",
        model_kwargs={'device': 'cpu'}
    )
    
    # 创建 Chroma 向量数据库
    vector_store = Chroma.from_documents(
        documents=documents,
        embedding=embeddings,
        persist_directory=persist_directory
    )
    
    vector_store.persist()
    return vector_store

这里选择的 all-MiniLM-L6-v2 是一个轻量级的嵌入模型,在 CPU 上也能快速运行。如果你有 GPU,可以换成 BAAI/bge-large-zh-v1.5 等中文效果更好的模型。

步骤四:构建 RAG 链

现在是最关键的部分——构建完整的 RAG 问答链:

from langchain_community.chat_models import ChatOllama
from langchain.chains import RetrievalQA
from langchain.prompts import PromptTemplate

def build_rag_chain(vector_store):
    """构建 RAG 问答链"""
    
    # 初始化本地大模型
    llm = ChatOllama(
        model="llama3",
        temperature=0.7
    )
    
    # 提示词模板
    prompt_template = """你是一个技术笔记助手。根据上下文回答问题。

上下文:
{context}

问题:{question}

如果上下文没有相关信息,直接说没有。"""

    prompt = PromptTemplate(
        template=prompt_template,
        input_variables=["context", "question"]
    )
    
    # 创建问答链
    qa_chain = RetrievalQA.from_chain_type(
        llm=llm,
        chain_type="stuff",
        retriever=vector_store.as_retriever(
            search_kwargs={"k": 3}  # 找出最相关的 3 个文档块
        ),
        chain_type_kwargs={"prompt": prompt},
        return_source_documents=True
    )
    
    return qa_chain

步骤五:运行完整流程

把所有模块组合在一起,形成完整的主程序:

import os

def main():
    # 配置路径
    NOTES_FOLDER = "./notes"
    VECTOR_DB_PATH = "./chroma_db"
    
    print("=" * 50)
    print("本地 RAG 问答系统")
    print("=" * 50)
    
    # 第一步:加载文档
    print("\n[1/3] 正在加载文档...")
    documents = load_documents(NOTES_FOLDER)
    print(f"共加载 {len(documents)} 个文档")
    
    # 第二步:分割文档
    print("\n[2/3] 正在分割文档...")
    chunks = split_documents(documents)
    print(f"分割成 {len(chunks)} 个文本块")
    
    # 第三步:创建向量存储
    print("\n[3/3] 正在构建向量索引...")
    if os.path.exists(VECTOR_DB_PATH):
        # 加载已有的向量数据库
        from langchain_community.vectorstores import Chroma
        from langchain_community.embeddings import HuggingFaceEmbeddings
        embeddings = HuggingFaceEmbeddings(
            model_name="sentence-transformers/all-MiniLM-L6-v2"
        )
        vector_store = Chroma(
            persist_directory=VECTOR_DB_PATH,
            embedding_function=embeddings
        )
    else:
        vector_store = create_vector_store(chunks, VECTOR_DB_PATH)
    print("向量索引构建完成!")
    
    # 构建问答链
    qa_chain = build_rag_chain(vector_store)
    
    # 交互式问答
    print("\n" + "=" * 50)
    print("系统准备就绪!请输入您的问题(输入 'quit' 退出)")
    print("=" * 50 + "\n")
    
    while True:
        question = input("问题: ").strip()
        if question.lower() == 'quit':
            break
        if not question:
            continue
            
        result = qa_chain({"query": question})
        
        print("\n" + "-" * 50)
        print("回答:")
        print(result["result"])
        print("-" * 50)
        
        # 显示参考的文档来源
        if result.get("source_documents"):
            print("\n参考来源:")
            for i, doc in enumerate(result["source_documents"], 1):
                print(f"  {i}. {doc.metadata.get('source', '未知来源')}")
        print()

if __name__ == "__main__":
    main()

运行结果

运行上述代码后,系统会先加载和索引文档。首次运行需要一些时间来完成向量化的过程。索引完成后,你可以开始提问:

问题: 我之前有没有记录过关于缓存的设计模式?

------------------------------------------------------------
回答:
根据你之前的技术笔记,你确实记录过缓存相关的设计模式。在 
2024-06-10 的「分布式系统设计原则」文档中,你提到了以下几种缓存策略:

1. Cache-Aside(旁路缓存):应用程序首先检查缓存,如果不存在则从数据库加载并写入缓存。
2. Write-Through:数据同时写入缓存和数据库,保证强一致性。
3. Write-Behind:异步写入数据库,性能高但可能存在数据丢失风险。
4. Redis 集群的主从复制配置,用于高可用场景。

这些模式在分布式系统中非常实用,建议根据具体业务场景选择合适的策略。

------------------------------------------------------------

参考来源:
1. ./notes/2024-06-10-分布式系统设计原则.md

可以看到,系统准确地找到了相关笔记,并基于内容生成了准确的回答。回答不仅指出了具体的设计模式,还引用了原始文档的位置。

总结

这篇文章带你走完了搭建本地 RAG 系统的全过程。总结一下这个方案的几个优点:

完全本地化——数据都存在自己电脑上,不用上传到任何第三方服务。

开源免费——不依赖商业 API,一台普通电脑就能跑。

灵活扩展——可以换不同的嵌入模型、大模型,也能支持更多文档类型。

后续优化方向:

  • 换成 BGE 等中文效果更好的嵌入模型
  • 添加 PDF、Word 格式支持
  • 实现增量索引,只更新变化了的文件
  • 做个 Web 界面,方便其他人用

RAG 最大的价值在于:让大模型真正变成懂你私有知识的助手。

暂无评论

发送评论 编辑评论


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