Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成)

Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成)


🌸你好呀!我是 lbb小魔仙
🌟 感谢陪伴~ 小白博主在线求友
🌿 跟着小白学Linux/Java/Python
📖 专栏汇总:
《Linux》专栏 | 《Java》专栏 | 《Python》专栏

  • [Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成)](#Python + LangChain 环境搭建完全指南:从零构建本地 RAG 知识库(附 Ollama 本地模型集成))
    • [1. 引言:什么是 RAG?为什么需要它?](#1. 引言:什么是 RAG?为什么需要它?)
    • [2. 环境准备](#2. 环境准备)
      • [2.1 系统与 Python 版本要求](#2.1 系统与 Python 版本要求)
      • [2.2 安装 Ollama 本地模型服务](#2.2 安装 Ollama 本地模型服务)
      • [2.3 创建 Python 虚拟环境](#2.3 创建 Python 虚拟环境)
    • [3. 安装 LangChain 及相关依赖](#3. 安装 LangChain 及相关依赖)
      • [3.1 核心包说明](#3.1 核心包说明)
      • [3.2 安装命令(含国内镜像加速)](#3.2 安装命令(含国内镜像加速))
      • [3.3 验证安装](#3.3 验证安装)
    • [4. LangChain 基础:接入本地 Ollama 模型](#4. LangChain 基础:接入本地 Ollama 模型)
      • [4.1 基础对话链](#4.1 基础对话链)
      • [4.2 提示词模板(PromptTemplate)](#4.2 提示词模板(PromptTemplate))
      • [4.3 对话记忆(Memory)](#4.3 对话记忆(Memory))
    • [5. 构建 RAG 知识库](#5. 构建 RAG 知识库)
      • [5.1 RAG 工作原理](#5.1 RAG 工作原理)
      • [5.2 文档加载与分块](#5.2 文档加载与分块)
      • [5.3 向量化与存储(ChromaDB)](#5.3 向量化与存储(ChromaDB))
      • [5.4 检索链构建](#5.4 检索链构建)
      • [5.5 完整 RAG 系统整合](#5.5 完整 RAG 系统整合)
    • [6. 进阶:多格式文档支持](#6. 进阶:多格式文档支持)
    • [7. 常见问题与解决方案](#7. 常见问题与解决方案)
      • [问题一:`pip install chromadb` 安装报错(Windows)](#问题一:pip install chromadb 安装报错(Windows))
      • [问题二:Embedding 向量化非常慢](#问题二:Embedding 向量化非常慢)
      • [问题三:RAG 回答不准确或出现"幻觉"](#问题三:RAG 回答不准确或出现"幻觉")
      • [问题四:`ImportError: cannot import name 'OllamaLLM' from 'langchain_community'`](#问题四:ImportError: cannot import name 'OllamaLLM' from 'langchain_community')
    • [8. 总结与拓展建议](#8. 总结与拓展建议)
      • [📊 本教程覆盖内容回顾](#📊 本教程覆盖内容回顾)
      • [🚀 拓展学习路径](#🚀 拓展学习路径)
      • [📚 参考资源](#📚 参考资源)

摘要:本教程手把手带你在 Windows / Ubuntu 22.04 上搭建 LangChain 开发环境,并使用 LangChain + Ollama 本地大模型 + ChromaDB 向量数据库,从零构建一个可以"读懂"你的 PDF / TXT 文档的本地 RAG(检索增强生成)知识库系统。全程数据不上云,保护隐私,附完整可运行代码。


1. 引言:什么是 RAG?为什么需要它?

大语言模型(LLM)本质上是一个"知识截止"系统------它只了解训练数据中包含的信息,对于你的私有文档(公司内部手册、个人笔记、产品文档等)一无所知。

RAG(Retrieval-Augmented Generation,检索增强生成) 正是解决这个问题的核心技术:

复制代码
┌─────────────────────────────────────────────────────┐
│                   RAG 工作流程                        │
│                                                      │
│  用户提问 → 向量检索相关文档片段 → 连同问题发给 LLM   │
│          → LLM 基于检索到的上下文生成准确回答         │
└─────────────────────────────────────────────────────┘
对比项 纯 LLM RAG + LLM
私有文档知识 ❌ 不了解 ✅ 完全支持
知识实时更新 ❌ 训练截止 ✅ 随时更新
回答幻觉 较多 大幅减少
数据隐私 数据上传云端 ✅ 本地处理
成本 按 Token 计费 ✅ 本地免费

本教程技术栈

  • LangChain:AI 应用开发框架(编排工具)
  • Ollama:本地大模型运行服务(推理引擎)
  • Qwen2.5 / LLaMA3:开源大语言模型(大脑)
  • ChromaDB:本地向量数据库(记忆存储)
  • nomic-embed-text:文本向量化模型(理解工具)

2. 环境准备

2.1 系统与 Python 版本要求

环境 最低版本 推荐版本
Python 3.9 3.11(推荐)
pip 21.0+ 最新版
操作系统 Windows 10 / Ubuntu 20.04 Windows 11 / Ubuntu 22.04
内存 8 GB 16 GB(运行 7B 模型)
bash 复制代码
# 检查 Python 版本(必须 ≥ 3.9)
python --version      # Windows
python3 --version     # Linux

# 升级 pip 到最新版(重要!旧版 pip 可能导致安装失败)
python -m pip install --upgrade pip

# 确认 pip 版本
pip --version
# 预期输出:pip 24.x.x from ...

2.2 安装 Ollama 本地模型服务

LangChain 需要通过 Ollama 来调用本地大模型和 Embedding 模型,请先完成 Ollama 安装:

bash 复制代码
# Linux 一键安装
curl -fsSL https://ollama.com/install.sh | sh

# 拉取对话模型(用于回答问题)
ollama pull qwen2.5:7b

# 拉取 Embedding 模型(用于文本向量化,RAG 核心)
ollama pull nomic-embed-text

# 验证模型已就绪
ollama list
# 预期输出:
# NAME                    ID              SIZE
# qwen2.5:7b             ...             4.7 GB
# nomic-embed-text       ...             274 MB

💡 提示nomic-embed-text 是专用文本向量化模型,文件小(274MB)、效果好,是本地 RAG 的首选 Embedding 模型。

2.3 创建 Python 虚拟环境

bash 复制代码
# 创建专用虚拟环境(强烈推荐,避免依赖版本冲突)
python -m venv langchain_env

# 激活虚拟环境
# Linux / macOS:
source langchain_env/bin/activate

# Windows(PowerShell):
langchain_env\Scripts\Activate.ps1

# Windows(命令提示符 CMD):
langchain_env\Scripts\activate.bat

# 确认虚拟环境已激活(命令行前缀应显示 (langchain_env))
# (langchain_env) PS C:\Users\...>

⚠️ 注意 :后续所有 pip install 命令都需要在激活虚拟环境后执行,否则包会安装到全局环境。


3. 安装 LangChain 及相关依赖

3.1 核心包说明

LangChain 生态由多个独立包组成,本项目用到以下几个:

包名 用途 备注
langchain LangChain 核心框架 必装
langchain-community 第三方集成(Ollama、ChromaDB 等) 必装
langchain-ollama Ollama 官方集成(新版专用包) 必装
chromadb 本地向量数据库 RAG 必装
langchain-chroma LangChain 与 ChromaDB 集成 RAG 必装
pypdf PDF 文档加载器 处理 PDF 必装
unstructured 多格式文档加载器 处理 Word/HTML 等

3.2 安装命令(含国内镜像加速)

bash 复制代码
# 方式一:使用清华镜像(国内推荐,速度快 5-10 倍)
pip install langchain langchain-community langchain-ollama \
            chromadb langchain-chroma \
            pypdf unstructured \
            -i https://pypi.tuna.tsinghua.edu.cn/simple

# 方式二:使用阿里云镜像
pip install langchain langchain-community langchain-ollama \
            chromadb langchain-chroma \
            pypdf unstructured \
            -i https://mirrors.aliyun.com/pypi/simple/

# 方式三:永久设置默认镜像(一劳永逸)
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
# 之后直接 pip install 即可自动使用镜像
pip install langchain langchain-community langchain-ollama chromadb langchain-chroma pypdf unstructured

⚠️ 常见坑chromadb 在安装时会编译 C++ 扩展,Windows 用户如遇错误请先安装 Microsoft C++ Build Tools

3.3 验证安装

python 复制代码
# verify_install.py
# 运行此脚本,确认所有依赖安装正确

def check_imports():
    checks = [
        ("langchain", "from langchain.schema import HumanMessage"),
        ("langchain-ollama", "from langchain_ollama import OllamaLLM"),
        ("chromadb", "import chromadb"),
        ("langchain-chroma", "from langchain_chroma import Chroma"),
        ("pypdf", "from langchain_community.document_loaders import PyPDFLoader"),
    ]
    
    print("=== 依赖包安装检查 ===\n")
    all_ok = True
    
    for name, import_stmt in checks:
        try:
            exec(import_stmt)
            print(f"  ✅ {name:<30} 安装正常")
        except ImportError as e:
            print(f"  ❌ {name:<30} 安装失败: {e}")
            all_ok = False
    
    print()
    if all_ok:
        print("🎉 所有依赖安装正常,可以开始使用!")
    else:
        print("⚠️  部分依赖安装失败,请根据提示重新安装")

check_imports()
bash 复制代码
# 运行检查脚本
python verify_install.py

# 预期输出:
# === 依赖包安装检查 ===
#
#   ✅ langchain                       安装正常
#   ✅ langchain-ollama                安装正常
#   ✅ chromadb                        安装正常
#   ✅ langchain-chroma                安装正常
#   ✅ pypdf                           安装正常
#
# 🎉 所有依赖安装正常,可以开始使用!

4. LangChain 基础:接入本地 Ollama 模型

4.1 基础对话链

python 复制代码
# 01_basic_llm.py
from langchain_ollama import OllamaLLM
from langchain_core.messages import HumanMessage, SystemMessage

# 初始化本地 Ollama 模型
llm = OllamaLLM(
    model="qwen2.5:7b",
    base_url="http://localhost:11434",  # Ollama 默认地址
    temperature=0.7,                    # 0.0=确定性输出,1.0=最具创造性
    num_predict=512,                    # 最大生成 Token 数
)

# 方式一:直接调用
response = llm.invoke("Python 中什么是装饰器?请举例说明")
print("LLM 回复:")
print(response)

# 方式二:使用 ChatOllama(支持角色消息)
from langchain_ollama import ChatOllama

chat_model = ChatOllama(
    model="qwen2.5:7b",
    temperature=0.7,
)

messages = [
    SystemMessage(content="你是一个专业的 Python 教学助手,解释要简洁易懂,并附上代码示例。"),
    HumanMessage(content="什么是 Python 生成器?")
]

response = chat_model.invoke(messages)
print("\nChatOllama 回复:")
print(response.content)

4.2 提示词模板(PromptTemplate)

提示词模板是 LangChain 的核心概念之一,用于动态构建 Prompt:

python 复制代码
# 02_prompt_template.py
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from langchain_core.output_parsers import StrOutputParser

# 初始化模型
llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)

# 方式一:ChatPromptTemplate(推荐)
chat_prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个专业的 {domain} 领域专家,回答要专业、准确、简洁。"),
    ("human", "{question}")
])

# 构建 LCEL 链(LangChain Expression Language)
# 语法:prompt | llm | parser,数据从左向右流动
chain = chat_prompt | llm | StrOutputParser()

# 调用链
response = chain.invoke({
    "domain": "Python 数据科学",
    "question": "pandas 中 groupby 操作的原理是什么?"
})
print("Chain 回复:")
print(response)


# 方式二:结构化提示词模板
code_review_prompt = ChatPromptTemplate.from_messages([
    ("system", """你是一个资深代码审查专家。
请对用户提供的代码进行审查,从以下维度给出建议:
1. 代码规范性
2. 潜在 Bug
3. 性能优化点
4. 可读性改进

输出格式:使用 Markdown 列表格式"""),
    ("human", "请审查以下 {language} 代码:\n\n```{language}\n{code}\n```")
])

review_chain = code_review_prompt | llm | StrOutputParser()

sample_code = """
def get_user_data(users):
    result = []
    for i in range(len(users)):
        if users[i]['age'] > 18:
            result.append(users[i])
    return result
"""

review = review_chain.invoke({
    "language": "Python",
    "code": sample_code
})
print("\n代码审查结果:")
print(review)

4.3 对话记忆(Memory)

python 复制代码
# 03_conversation_memory.py
from langchain_ollama import ChatOllama
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory

# 初始化模型
llm = ChatOllama(model="qwen2.5:7b", temperature=0.7)

# 构建支持历史记录的提示词模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "你是一个友好的 AI 助手。"),
    MessagesPlaceholder(variable_name="history"),  # 历史消息占位符
    ("human", "{input}")
])

# 构建基础链
chain = prompt | llm

# 存储每个会话的历史记录
session_store = {}

def get_session_history(session_id: str) -> InMemoryChatMessageHistory:
    """根据 session_id 获取或创建对话历史"""
    if session_id not in session_store:
        session_store[session_id] = InMemoryChatMessageHistory()
    return session_store[session_id]

# 将链包装成支持历史记录的版本
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history"
)

# 配置(每次调用需传入 session_id)
config = {"configurable": {"session_id": "user_001"}}

print("=== 多轮对话测试 ===\n")

# 第一轮
response1 = chain_with_history.invoke(
    {"input": "我叫小明,我正在学习 Python"},
    config=config
)
print(f"第一轮回复:{response1.content}\n")

# 第二轮(AI 能记住上文的"小明")
response2 = chain_with_history.invoke(
    {"input": "我之前说我叫什么名字?"},
    config=config
)
print(f"第二轮回复:{response2.content}\n")

# 第三轮
response3 = chain_with_history.invoke(
    {"input": "给我推荐一个适合我学习的 Python 项目"},
    config=config
)
print(f"第三轮回复:{response3.content}\n")

5. 构建 RAG 知识库

5.1 RAG 工作原理

在编写代码前,先理解 RAG 的完整流程:

复制代码
【离线阶段:知识入库】
文档文件(PDF/TXT/MD)
    ↓ 文档加载器(DocumentLoader)
原始文本
    ↓ 文本分割器(TextSplitter)
文本块(Chunks)
    ↓ Embedding 模型(nomic-embed-text)
向量(Vectors)
    ↓ 存入向量数据库(ChromaDB)
向量索引(持久化到本地)

【在线阶段:问答检索】
用户提问
    ↓ Embedding 模型(同上)
问题向量
    ↓ 相似度搜索(ChromaDB)
相关文档片段(Top-K)
    ↓ 组合成 Prompt(RAG Prompt)
最终 Prompt = 问题 + 相关上下文
    ↓ 大语言模型(qwen2.5:7b)
最终答案

5.2 文档加载与分块

python 复制代码
# 04_document_loading.py
from langchain_community.document_loaders import (
    PyPDFLoader,       # PDF 文件
    TextLoader,        # TXT 文件
    DirectoryLoader,   # 批量加载目录
)
from langchain.text_splitter import RecursiveCharacterTextSplitter

# ---- 加载单个 PDF 文件 ----
def load_pdf(file_path: str):
    loader = PyPDFLoader(file_path)
    pages = loader.load()
    print(f"PDF 加载完成:共 {len(pages)} 页")
    print(f"第一页内容预览:{pages[0].page_content[:200]}...")
    return pages

# ---- 加载 TXT 文件 ----
def load_txt(file_path: str):
    loader = TextLoader(file_path, encoding="utf-8")
    docs = loader.load()
    return docs

# ---- 批量加载目录中的所有 TXT 文件 ----
def load_directory(dir_path: str):
    loader = DirectoryLoader(
        dir_path,
        glob="**/*.txt",           # 匹配所有 TXT 文件
        loader_cls=TextLoader,
        loader_kwargs={"encoding": "utf-8"}
    )
    docs = loader.load()
    print(f"目录加载完成:共 {len(docs)} 个文档")
    return docs

# ---- 文本分块(核心步骤)----
def split_documents(docs, chunk_size=500, chunk_overlap=50):
    """
    将文档切割成小块
    
    Args:
        docs: 文档列表
        chunk_size: 每块的字符数(推荐 300~1000)
        chunk_overlap: 相邻块的重叠字符数(保证上下文连续性)
    
    Returns:
        切割后的文档块列表
    """
    splitter = RecursiveCharacterTextSplitter(
        chunk_size=chunk_size,         # 每块最大字符数
        chunk_overlap=chunk_overlap,   # 重叠量,避免切断语义
        length_function=len,
        separators=["\n\n", "\n", "。", "!", "?", " ", ""],  # 优先按段落切
    )
    
    chunks = splitter.split_documents(docs)
    print(f"文档分块完成:{len(docs)} 个文档 → {len(chunks)} 个文本块")
    print(f"平均块大小:{sum(len(c.page_content) for c in chunks) // len(chunks)} 字符")
    return chunks


# ---- 演示(使用临时文件测试)----
if __name__ == "__main__":
    import os
    
    # 创建一个测试文档
    test_content = """
    LangChain 是一个用于构建大语言模型应用的开源框架。
    
    它的核心功能包括:
    1. 模型集成:支持 OpenAI、Anthropic、Ollama 等主流模型
    2. 提示词管理:灵活的 PromptTemplate 系统
    3. 链式调用:通过 LCEL 组合多个组件
    4. 记忆管理:支持多轮对话上下文保存
    5. 文档检索:内置 RAG 支持,轻松构建知识库
    
    RAG(检索增强生成)是一种将外部知识库与大语言模型结合的技术。
    通过向量检索找到最相关的文档片段,并将其作为上下文注入到提示词中,
    从而让模型能够回答关于私有数据的问题,同时减少模型"幻觉"。
    """
    
    with open("test_doc.txt", "w", encoding="utf-8") as f:
        f.write(test_content)
    
    docs = load_txt("test_doc.txt")
    chunks = split_documents(docs, chunk_size=200, chunk_overlap=30)
    
    print(f"\n分块示例(第1块):")
    print(chunks[0].page_content)
    print(f"\n元数据:{chunks[0].metadata}")

5.3 向量化与存储(ChromaDB)

python 复制代码
# 05_vector_store.py
from langchain_ollama import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import TextLoader
import os

# ---- 初始化 Embedding 模型 ----
embeddings = OllamaEmbeddings(
    model="nomic-embed-text",        # 本地 Embedding 模型(需先 ollama pull nomic-embed-text)
    base_url="http://localhost:11434"
)

# 测试 Embedding(确认服务正常)
test_embedding = embeddings.embed_query("测试文本")
print(f"Embedding 维度:{len(test_embedding)}")  # nomic-embed-text 输出 768 维向量


# ---- 构建向量数据库 ----
def build_vector_store(documents, persist_dir: str = "./chroma_db"):
    """
    将文档向量化并存储到 ChromaDB
    
    Args:
        documents: 文档块列表
        persist_dir: 数据库持久化路径(本地磁盘)
    
    Returns:
        Chroma 向量数据库实例
    """
    print(f"正在向量化 {len(documents)} 个文本块,请稍候...")
    
    vector_store = Chroma.from_documents(
        documents=documents,
        embedding=embeddings,
        persist_directory=persist_dir,  # 持久化到本地,重启后无需重新建库
        collection_name="my_knowledge_base"
    )
    
    print(f"✅ 向量库构建完成,已保存至: {persist_dir}")
    print(f"   共存储 {vector_store._collection.count()} 个向量")
    return vector_store


# ---- 加载已有向量数据库 ----
def load_vector_store(persist_dir: str = "./chroma_db"):
    """
    从磁盘加载已构建好的向量数据库(无需重新向量化)
    """
    if not os.path.exists(persist_dir):
        raise FileNotFoundError(f"向量数据库不存在: {persist_dir},请先运行 build_vector_store()")
    
    vector_store = Chroma(
        persist_directory=persist_dir,
        embedding_function=embeddings,
        collection_name="my_knowledge_base"
    )
    print(f"✅ 向量库加载成功:{vector_store._collection.count()} 个向量")
    return vector_store


# ---- 相似度检索测试 ----
def test_retrieval(vector_store, query: str, top_k: int = 3):
    """
    测试向量检索效果
    
    Args:
        vector_store: Chroma 实例
        query: 查询问题
        top_k: 返回最相关的 K 个文档块
    """
    results = vector_store.similarity_search_with_score(query, k=top_k)
    
    print(f"\n查询:{query}")
    print(f"检索到 {len(results)} 个相关片段:\n")
    
    for i, (doc, score) in enumerate(results, 1):
        print(f"【相关片段 {i}】相似度得分: {1 - score:.4f}")
        print(f"内容: {doc.page_content[:200]}...")
        print(f"来源: {doc.metadata.get('source', '未知')}")
        print()
    
    return [doc for doc, _ in results]

5.4 检索链构建

python 复制代码
# 06_retrieval_chain.py
from langchain_ollama import ChatOllama
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 初始化组件
llm = ChatOllama(model="qwen2.5:7b", temperature=0.3)  # 知识库问答用低 temperature,更准确

embeddings = OllamaEmbeddings(model="nomic-embed-text")

# RAG 专用提示词模板
RAG_PROMPT = ChatPromptTemplate.from_messages([
    ("system", """你是一个知识库问答助手。
请根据以下【参考资料】来回答用户的问题。
回答要求:
- 只根据参考资料中的内容回答,不要添加资料中没有的信息
- 如果参考资料中没有相关信息,请明确说明"参考资料中未找到相关信息"
- 回答要准确、简洁,引用原文时加引号

【参考资料】
{context}
"""),
    ("human", "{question}")
])

def format_docs(docs) -> str:
    """将检索到的文档块格式化为字符串"""
    return "\n\n---\n\n".join([
        f"[来源: {doc.metadata.get('source', '未知')}]\n{doc.page_content}"
        for doc in docs
    ])

def build_rag_chain(vector_store, top_k: int = 4):
    """
    构建 RAG 检索链
    
    Args:
        vector_store: 向量数据库实例
        top_k: 每次检索返回的最相关文档数
    
    Returns:
        LCEL RAG 链
    """
    # 创建检索器
    retriever = vector_store.as_retriever(
        search_type="similarity",      # 相似度搜索
        search_kwargs={"k": top_k}     # 返回前 K 个结果
    )
    
    # 构建 LCEL 链
    # RunnablePassthrough:透传 question 字段
    # retriever → format_docs:检索并格式化上下文
    rag_chain = (
        {
            "context": retriever | format_docs,  # 检索相关文档作为上下文
            "question": RunnablePassthrough()    # 用户问题直接传递
        }
        | RAG_PROMPT    # 组合成完整 Prompt
        | llm           # 发给 LLM 生成回答
        | StrOutputParser()  # 提取纯文本输出
    )
    
    return rag_chain

5.5 完整 RAG 系统整合

python 复制代码
# rag_system.py  ← 主程序,整合所有模块
"""
完整的本地 RAG 知识库系统
使用方式:
1. 将你的文档放入 ./docs/ 目录(支持 .txt / .pdf)
2. 运行此脚本,首次运行会自动建库
3. 向 AI 提问,它会基于你的文档回答
"""

import os
from pathlib import Path

from langchain_community.document_loaders import (
    PyPDFLoader, TextLoader, DirectoryLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_chroma import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough


# ====== 配置项 ======
DOCS_DIR = "./docs"           # 文档目录
CHROMA_DIR = "./chroma_db"    # 向量库存储目录
EMBED_MODEL = "nomic-embed-text"
CHAT_MODEL = "qwen2.5:7b"
CHUNK_SIZE = 500
CHUNK_OVERLAP = 50
TOP_K = 4
# ====================


class LocalRAGSystem:
    """
    本地 RAG 知识库系统
    支持 PDF 和 TXT 格式文档
    """
    
    def __init__(self):
        self.embeddings = OllamaEmbeddings(model=EMBED_MODEL)
        self.llm = ChatOllama(model=CHAT_MODEL, temperature=0.3)
        self.vector_store = None
        self.rag_chain = None
    
    def _load_documents(self) -> list:
        """加载 docs 目录下的所有文档"""
        docs = []
        docs_path = Path(DOCS_DIR)
        
        if not docs_path.exists():
            docs_path.mkdir(parents=True)
            print(f"已创建文档目录: {DOCS_DIR},请放入你的文档后重新运行")
            return []
        
        # 加载 TXT 文件
        for txt_file in docs_path.rglob("*.txt"):
            loader = TextLoader(str(txt_file), encoding="utf-8")
            docs.extend(loader.load())
            print(f"  已加载 TXT: {txt_file.name}")
        
        # 加载 PDF 文件
        for pdf_file in docs_path.rglob("*.pdf"):
            loader = PyPDFLoader(str(pdf_file))
            docs.extend(loader.load())
            print(f"  已加载 PDF: {pdf_file.name}")
        
        return docs
    
    def _split_documents(self, docs: list) -> list:
        """文档分块"""
        splitter = RecursiveCharacterTextSplitter(
            chunk_size=CHUNK_SIZE,
            chunk_overlap=CHUNK_OVERLAP,
            separators=["\n\n", "\n", "。", "!", "?", " ", ""]
        )
        return splitter.split_documents(docs)
    
    def build_or_load_db(self) -> None:
        """构建或加载向量数据库"""
        if os.path.exists(CHROMA_DIR):
            print(f"📂 加载已有向量库: {CHROMA_DIR}")
            self.vector_store = Chroma(
                persist_directory=CHROMA_DIR,
                embedding_function=self.embeddings,
                collection_name="knowledge_base"
            )
            count = self.vector_store._collection.count()
            print(f"✅ 向量库加载完成,共 {count} 个向量")
        else:
            print("🔨 首次运行,开始构建向量库...")
            print(f"加载 {DOCS_DIR} 目录下的文档...")
            
            raw_docs = self._load_documents()
            if not raw_docs:
                return
            
            print(f"文档分块中(chunk_size={CHUNK_SIZE})...")
            chunks = self._split_documents(raw_docs)
            print(f"分块完成:{len(chunks)} 个文本块")
            
            print("向量化并存储(首次可能需要几分钟)...")
            self.vector_store = Chroma.from_documents(
                documents=chunks,
                embedding=self.embeddings,
                persist_directory=CHROMA_DIR,
                collection_name="knowledge_base"
            )
            print(f"✅ 向量库构建完成!已保存至 {CHROMA_DIR}")
    
    def _build_rag_chain(self):
        """构建 RAG 问答链"""
        RAG_PROMPT = ChatPromptTemplate.from_messages([
            ("system", """你是一个专业的知识库问答助手。
请严格基于以下【参考资料】回答用户问题。

规则:
1. 只使用参考资料中的信息
2. 如果资料中没有答案,回复"我在知识库中未找到相关信息"
3. 回答要准确、简洁,可适当引用原文

【参考资料】
{context}
"""),
            ("human", "问题:{question}")
        ])
        
        retriever = self.vector_store.as_retriever(
            search_kwargs={"k": TOP_K}
        )
        
        def format_docs(docs):
            return "\n\n---\n\n".join([
                f"[来源文件: {doc.metadata.get('source', '未知')}]\n{doc.page_content}"
                for doc in docs
            ])
        
        self.rag_chain = (
            {"context": retriever | format_docs, "question": RunnablePassthrough()}
            | RAG_PROMPT
            | self.llm
            | StrOutputParser()
        )
    
    def ask(self, question: str) -> str:
        """向知识库提问"""
        if self.vector_store is None:
            return "错误:向量库未初始化,请先调用 build_or_load_db()"
        
        if self.rag_chain is None:
            self._build_rag_chain()
        
        return self.rag_chain.invoke(question)
    
    def add_document(self, file_path: str) -> None:
        """向现有知识库添加新文档(增量更新)"""
        if file_path.endswith(".pdf"):
            loader = PyPDFLoader(file_path)
        else:
            loader = TextLoader(file_path, encoding="utf-8")
        
        docs = loader.load()
        chunks = self._split_documents(docs)
        self.vector_store.add_documents(chunks)
        print(f"✅ 已添加 {len(chunks)} 个文本块到知识库:{file_path}")
    
    def run_interactive(self):
        """启动交互式问答"""
        print("\n" + "=" * 55)
        print("  📚 本地 RAG 知识库问答系统(基于 LangChain + Ollama)")
        print("=" * 55)
        print("输入问题与知识库对话,输入 'quit' 退出\n")
        
        while True:
            question = input("❓ 你的问题: ").strip()
            
            if not question:
                continue
            if question.lower() == "quit":
                print("再见!👋")
                break
            
            print("\n🔍 检索中...", end="", flush=True)
            answer = self.ask(question)
            print(f"\r💡 回答:\n{answer}\n")


def main():
    rag = LocalRAGSystem()
    rag.build_or_load_db()
    
    if rag.vector_store:
        rag.run_interactive()


if __name__ == "__main__":
    main()

6. 进阶:多格式文档支持

python 复制代码
# multi_format_loader.py
from langchain_community.document_loaders import (
    PyPDFLoader,                    # PDF
    TextLoader,                     # TXT
    UnstructuredMarkdownLoader,     # Markdown
    UnstructuredWordDocumentLoader, # Word (.docx)
    UnstructuredHTMLLoader,         # HTML
    CSVLoader,                      # CSV
    JSONLoader,                     # JSON
    WebBaseLoader,                  # 网页
)
import os

def load_document_by_type(file_path: str):
    """
    根据文件扩展名自动选择加载器
    
    Args:
        file_path: 文件路径
    
    Returns:
        Document 列表
    """
    ext = os.path.splitext(file_path)[-1].lower()
    
    loader_map = {
        ".pdf":  lambda p: PyPDFLoader(p),
        ".txt":  lambda p: TextLoader(p, encoding="utf-8"),
        ".md":   lambda p: UnstructuredMarkdownLoader(p),
        ".docx": lambda p: UnstructuredWordDocumentLoader(p),
        ".html": lambda p: UnstructuredHTMLLoader(p),
        ".csv":  lambda p: CSVLoader(p, encoding="utf-8"),
    }
    
    if ext not in loader_map:
        raise ValueError(f"不支持的文件格式: {ext}")
    
    loader = loader_map[ext](file_path)
    docs = loader.load()
    print(f"✅ 加载 {ext} 文件成功:{os.path.basename(file_path)}({len(docs)} 页/段)")
    return docs


# 加载网页内容并添加到知识库
def load_web_page(url: str):
    """
    加载网页内容(如官方文档、博客等)
    """
    loader = WebBaseLoader(url)
    docs = loader.load()
    print(f"✅ 已加载网页: {url}")
    return docs

7. 常见问题与解决方案

问题一:pip install chromadb 安装报错(Windows)

错误信息

复制代码
error: Microsoft Visual C++ 14.0 or greater is required

原因:chromadb 需要编译 C++ 扩展,Windows 缺少编译工具

解决方案

bash 复制代码
# 方法一:安装 Microsoft C++ Build Tools
# 下载地址:https://visualstudio.microsoft.com/visual-cpp-build-tools/
# 安装时勾选"使用 C++ 的桌面开发"工作负载

# 方法二:使用预编译 wheel(跳过编译)
pip install chromadb --only-binary chromadb

# 方法三:升级 pip 后重试
pip install --upgrade pip setuptools wheel
pip install chromadb

问题二:Embedding 向量化非常慢

原因nomic-embed-text 模型首次加载需要时间;大量文档时向量化耗时较长

优化方案

python 复制代码
# 方法一:批量向量化,减少重复加载开销
from langchain_chroma import Chroma

# 一次性传入所有 chunks,比逐条插入快得多
vector_store = Chroma.from_documents(
    documents=all_chunks,   # 一次性传入全部文档块
    embedding=embeddings,
    persist_directory="./chroma_db"
)

# 方法二:增加批次大小(默认为 1)
embeddings = OllamaEmbeddings(
    model="nomic-embed-text",
    # 较新版本支持 batch_size 参数
)

问题三:RAG 回答不准确或出现"幻觉"

原因和解决方案

python 复制代码
# 原因一:检索 top_k 太少,相关内容未被找到
# 解决:增加 top_k 值
retriever = vector_store.as_retriever(search_kwargs={"k": 6})  # 从 4 增加到 6

# 原因二:文本块太大,稀释了相关信息
# 解决:减小 chunk_size
splitter = RecursiveCharacterTextSplitter(
    chunk_size=300,    # 从 500 减小到 300
    chunk_overlap=50,
)

# 原因三:Prompt 设计不够严格
# 解决:加强系统提示词中的约束
system_prompt = """严格基于以下参考资料回答问题。
如果参考资料中没有相关信息,必须明确说"未找到相关信息",
绝对不可以根据自身知识补充未在资料中出现的内容。"""

问题四:ImportError: cannot import name 'OllamaLLM' from 'langchain_community'

原因 :新版 LangChain 将 Ollama 集成独立为 langchain-ollama

解决方案

bash 复制代码
# 安装新版专用包
pip install langchain-ollama

# 修改 import 语句
# 旧写法(已废弃):
# from langchain_community.llms import Ollama

# 新写法(推荐):
from langchain_ollama import OllamaLLM, ChatOllama, OllamaEmbeddings

8. 总结与拓展建议

📊 本教程覆盖内容回顾

章节 核心内容
环境准备 Python 虚拟环境、Ollama 模型安装
依赖安装 LangChain + ChromaDB + 国内镜像加速
基础对话 OllamaLLM / ChatOllama 调用
提示词模板 PromptTemplate + LCEL 链
对话记忆 多轮对话历史管理
RAG 知识库 文档加载 → 分块 → 向量化 → 检索 → 问答
多格式支持 PDF / TXT / DOCX / 网页
常见问题 4 个高频报错 + 解决方案

🚀 拓展学习路径

完成本教程后,推荐继续深入:

  1. LangChain Agent:让 AI 自主调用工具(搜索、计算、代码执行),实现自主决策
  2. 流式 RAG:结合 FastAPI 构建支持流式输出的 RAG 服务接口
  3. Rerank 重排序:引入 BGE-Reranker 对检索结果二次排序,提升准确率
  4. 多向量库:使用 FAISS(Meta 开源)替换 ChromaDB,性能更高
  5. 评估体系:使用 RAGAS 框架评估 RAG 系统的准确率和召回率

💡 关注博主 ,后续将更新 LangChain Agent 实战本地知识库接入 Web 界面等进阶教程!


📚 参考资源


如果本文对你有帮助,欢迎点赞 👍 + 收藏 ⭐ + 关注!有问题欢迎在评论区留言,博主会及时回复 💬
📕个人领域 :Linux/C++/java/AI

🚀 个人主页有点流鼻涕 · CSDN

💬 座右铭 : "向光而行,沐光而生。"

相关推荐
风落无尘1 小时前
Python 包发布全流程:从项目结构到 PyPI 上线,以及我踩过的那些坑
开发语言·python·pip
xxjj998a1 小时前
PHP vs C#:两大编程语言终极对比
开发语言·c#·php
Lenyiin1 小时前
《LeetCode 顺序刷题》61 - 70
java·c++·python·算法·leetcode·lenyiin
岁岁的O泡奶1 小时前
NSSCTF_crypto_[LitCTF 2023]babyLCG
经验分享·python·算法·密码学·crypto·流密码
风落无尘1 小时前
我用 LangChain 写了一个带“定速巡航”的向量化工具,发布到 PyPI 了!
人工智能·python·langchain
AI技术控1 小时前
RAG 效果差不是模型问题:10 个检索增强失败原因总结
人工智能·python·自然语言处理
敲代码的瓦龙2 小时前
Android?基础UI控件!!!
java·开发语言
Hesionberger2 小时前
LeetCode 78:子集生成全攻略
java·开发语言·数据结构·python·算法·leetcode·职场和发展
bzmK1DTbd2 小时前
Swagger API文档:Java RESTful服务的自动生成
java·开发语言·restful