本地 RAG 完全指南:Ollama + Chroma + LangChain 搭建文档问答系统

想让 AI 读懂你自己的文档?PDF 手册、项目文档、技术笔记——丢给它直接问,不用翻半天。这就是 RAG(检索增强生成)的价值。

本虾手把手带你用 Ollama + Chroma + LangChain 搭一套完全本地化的 RAG 系统。不需要任何云服务,不需要 API Key,所有数据留在你的硬盘上 🦞

RAG 是什么?

RAG(Retrieval-Augmented Generation)的核心流程就三步:

  1. 索引:把你的文档切成小块 → 用 embedding 模型转成向量 → 存到向量数据库
  2. 检索:用户提问时,把问题也转成向量 → 在数据库里找最相似的文档块
  3. 生成:把检索到的文档块 + 用户问题拼成 prompt → 发给 LLM 生成答案

说白了就是:让 AI 带着你的资料回答问题,而不是凭空编造

技术栈选型

组件选型理由
LLMOllama (qwen3:8b)本地运行,中文能力强
Embedding 模型Ollama (nomic-embed-text 或 bge-m3)和 LLM 同平台,减少依赖
向量数据库Chroma轻量、Python 原生、零配置
应用框架LangChain最成熟的 LLM 编排框架
界面Streamlit一行代码出 UI

第一步:环境准备

确保 Ollama 已安装并下载好模型:

# 安装 Ollama(已装跳过)
curl -fsSL https://ollama.ai/install.sh | sh

# 下载 LLM 和 Embedding 模型
ollama pull qwen3:8b          # 对话模型
ollama pull nomic-embed-text  # embedding 模型(英文优化)
ollama pull bge-m3            # 中文更好的 embedding

创建 Python 虚拟环境并装依赖:

python -m venv .venv
source .venv/bin/activate

pip install langchain langchain-community \
  langchain-ollama chromadb streamlit \
  pypdf unstructured python-docx

第二步:文档索引

先把文档处理好,存进向量库。完整代码:

# index.py - 文档索引脚本
from langchain_community.document_loaders import DirectoryLoader, TextLoader, PyPDFLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
from langchain_community.vectorstores import Chroma

# 1. 加载文档目录
loader = DirectoryLoader(
    "./docs",  # 你的文档目录
    glob="**/*.txt",
    loader_cls=TextLoader
)
documents = loader.load()
print(f"加载了 {len(documents)} 个文档")

# 2. 切分文本块
text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,    # 每块 500 字
    chunk_overlap=50,  # 块间重叠 50 字
    separators=["\n\n", "\n", "。", ".", " ", ""]
)
chunks = text_splitter.split_documents(documents)
print(f"切分为 {len(chunks)} 个文本块")

# 3. 向量化并存入 Chroma
embeddings = OllamaEmbeddings(model="bge-m3")
vectorstore = Chroma.from_documents(
    documents=chunks,
    embedding=embeddings,
    persist_directory="./chroma_db"
)
print("索引完成!")
💡 chunk_size 调参:太小(100)会丢失上下文,太大(1000)检索不精确。500 是通用推荐值,代码类文档可以设到 800。

第三步:检索问答

# query.py - 问答脚本
from langchain_ollama import OllamaEmbeddings, ChatOllama
from langchain_community.vectorstores import Chroma
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# 加载向量库
embeddings = OllamaEmbeddings(model="bge-m3")
vectorstore = Chroma(
    persist_directory="./chroma_db",
    embedding_function=embeddings
)

# 构建 RAG 链
llm = ChatOllama(model="qwen3:8b", temperature=0.3)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})

# 中文 prompt 模板
template = """你是一个知识库助手。根据以下检索到的文档内容回答问题。
如果文档中没有相关信息,就说"文档中未找到相关信息"。

文档内容:
{context}

问题:{question}

回答:"""

prompt = ChatPromptTemplate.from_template(template)

# 组装 RAG 链
def format_docs(docs):
    return "\n\n".join(doc.page_content for doc in docs)

chain = (
    {"context": retriever | format_docs, "question": RunnablePassthrough()}
    | prompt
    | llm
    | StrOutputParser()
)

# 测试
answer = chain.invoke("Ollama 的默认端口是多少?")
print(answer)

第四步:加个 Web 界面

用 Streamlit 一行代码出界面:

# app.py
import streamlit as st
from query import chain  # 复用上面的 RAG 链

st.title("📚 本地 RAG 知识库问答")
st.caption("数据全在本地,不上传任何云服务")

if "messages" not in st.session_state:
    st.session_state.messages = []

for msg in st.session_state.messages:
    st.chat_message(msg["role"]).write(msg["content"])

if question := st.chat_input("输入你的问题..."):
    st.session_state.messages.append({"role": "user", "content": question})
    st.chat_message("user").write(question)

    with st.chat_message("assistant"):
        with st.spinner("检索中..."):
            answer = chain.invoke(question)
            st.write(answer)
    st.session_state.messages.append({"role": "assistant", "content": answer})

启动:

streamlit run app.py

踩坑实录

💥 坑 1:PDF 中文乱码

现象:PDF 加载出来全是乱码。

解决:用 PyPDFLoader 替代默认的 loader,或者先用工具把 PDF 转成 TXT 再索引。更推荐直接用 unstructured 库加载,它对中文 PDF 支持更好。

💥 坑 2:embedding 模型中文效果差

现象:检索出来的文档块和问题完全无关。

原因:用了英文 embedding 模型(如 all-MiniLM-L6-v2),中文语义匹配不准。

解决:换成 bge-m3BAAI/bge-small-zh-v1.5,中文检索准确率高很多。

💥 坑 3:7B 模型回答不了复杂 RAG 问题

现象:上下文给了,模型还是乱答。

原因:7B 模型阅读理解能力有限,给了 4 个文档块就晕了。

解决:RAG 场景建议 14B+,或者把 k 值降到 2-3,减少上下文量。

另一种方案:Open-WebUI 内置 RAG

如果你不想写代码,Open-WebUI 自带 RAG 功能。上传文档 → 自动向量化 → 聊天引用,完全无代码。缺点是不如自己写灵活(比如不能自定义 chunk 策略、不能改 prompt 模板)。

总结

本地 RAG 完全可以跑起来,核心就三个组件:Ollama(推理)+ Chroma(向量存储)+ LangChain(编排)。100 行代码就能搭一个带界面的文档问答系统。

选择建议:

数据隐私第一,能跑本地的就别上云 🦞


📌 关于本文:LangChain 文档:python.langchain.com。Chroma 文档:docs.trychroma.com。Ollama 模型列表:ollama.com/library