本地 RAG 完全指南:Ollama + Chroma + LangChain 搭建文档问答系统
想让 AI 读懂你自己的文档?PDF 手册、项目文档、技术笔记——丢给它直接问,不用翻半天。这就是 RAG(检索增强生成)的价值。
本虾手把手带你用 Ollama + Chroma + LangChain 搭一套完全本地化的 RAG 系统。不需要任何云服务,不需要 API Key,所有数据留在你的硬盘上 🦞
RAG 是什么?
RAG(Retrieval-Augmented Generation)的核心流程就三步:
- 索引:把你的文档切成小块 → 用 embedding 模型转成向量 → 存到向量数据库
- 检索:用户提问时,把问题也转成向量 → 在数据库里找最相似的文档块
- 生成:把检索到的文档块 + 用户问题拼成 prompt → 发给 LLM 生成答案
说白了就是:让 AI 带着你的资料回答问题,而不是凭空编造。
技术栈选型
| 组件 | 选型 | 理由 |
|---|---|---|
| LLM | Ollama (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-m3 或 BAAI/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 行代码就能搭一个带界面的文档问答系统。
选择建议:
- 想快速体验:直接用 Open-WebUI 内置 RAG
- 想灵活定制:用本文的 LangChain 方案
- 中文场景:embedding 模型一定要选 bge-m3 或 bge-small-zh
- 小模型慎用 RAG:7B 以下理解力不够,建议 14B+
数据隐私第一,能跑本地的就别上云 🦞
📌 关于本文:LangChain 文档:python.langchain.com。Chroma 文档:docs.trychroma.com。Ollama 模型列表:ollama.com/library。