背景介绍
大语言模型(LLM)现在应用很广,但直接用它有一些问题:知识库是固定的、专业知识不够、偶尔会胡扯。检索增强生成(RAG)技术把检索和生成结合起来,先从知识库里找相关信息,再把这些内容喂给 LLM,这样生成的答案更靠谱。以前的 RAG 方案基本都调用 OpenAI API,得花钱,还有数据隐私的破事。
这两年开源出了不少能在本地跑的方案。Ollama 能在自己机器上跑各种开源大模型,LangChain 打包了 RAG 开发的全套工具,Chroma 是轻量级向量数据库。这三个凑一起,就能在本地搭一个 RAG 系统,不用联网,数据全存自己电脑上。
问题描述
很多人会遇到这种情况:基于公司内部文档做个问答系统,数据不想传到云上,就想全放本地。这种需求下,本地 RAG 系统就很合适。
这篇讲清楚怎么从零开始,用 Ollama 跑开源模型(Llama 3、Qwen 什么的),配合 LangChain 的 RAG 组件和 Chroma 向量数据库,搭一个本地问答系统。整个过程不需要互联网,满足企业数据安全的要求。
详细步骤
环境准备
先装必要的 Python 包。Python 3.10 或更高版本。建个虚拟环境,然后装依赖:
pip install langchain langchain-community langchain-chroma ollama chromadb beautifulsoup4 requests
安装并启动 Ollama
Ollama 支持 macOS、Linux 和 Windows。Linux 上用一键脚本安装:
curl -fsSL https://ollama.com/install.sh | sh
装完下模型。推荐 qwen2.5 或 llama3,还有 nomic-embed-text 用来做文本嵌入:
ollama pull qwen2.5:7b
ollama pull nomic-embed-text
启动服务:
ollama serve
准备知识文档
搞几个文本文件放到目录里当知识库。Markdown、TXT、HTML 都行。每个文件算一个知识主题。
构建 RAG 应用
流程是这样的:
- 加载文档 — 用 LangChain 的文档加载器读本地文件
- 分割 — 把文档切成小块,方便后面检索
- 向量化 — 用嵌入模型把文本块变成向量,存到 Chroma
- 检索 — 把用户问题转成向量,从数据库找最相似的文本块
- 生成 — 把检索到的内容和问题一起发给 LLM,拿答案
完整代码示例
#!/usr/bin/env python3
"""
本地 RAG 问答系统
使用 Ollama + LangChain + Chroma
"""
import os
from pathlib import Path
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_community.llms import Ollama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain import hub
# 配置参数
DOCS_DIR = "./docs" # 知识文档目录
VECTORSTORE_DIR = "./vectorstore" # 向量数据库存储路径
LLM_MODEL = "qwen2.5:7b" # 使用的 LLM 模型
EMBED_MODEL = "nomic-embed-text" # 嵌入模型
def load_documents(docs_dir: str):
"""加载目录中的所有文档"""
documents = []
docs_path = Path(docs_dir)
if not docs_path.exists():
raise FileNotFoundError(f"文档目录不存在: {docs_dir}")
# 支持多种文本格式
for ext in ["*.txt", "*.md", "*.html"]:
for file_path in docs_path.glob(ext):
loader = TextLoader(str(file_path), encoding="utf-8")
docs = loader.load()
documents.extend(docs)
print(f"已加载 {len(documents)} 个文档")
return documents
def split_documents(documents):
"""将文档分割成小块"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
length_function=len,
)
chunks = text_splitter.split_documents(documents)
print(f"文档分割完成,共 {len(chunks)} 个文本块")
return chunks
def create_vectorstore(chunks, persist_dir: str):
"""创建向量数据库"""
# 使用 Ollama 的嵌入模型
embeddings = OllamaEmbeddings(
model=EMBED_MODEL,
base_url="http://localhost:11434"
)
# 如果向量数据库已存在,直接加载
if os.path.exists(persist_dir):
print("加载已存在的向量数据库...")
vectorstore = Chroma(
persist_directory=persist_dir,
embedding_function=embeddings
)
else:
print("创建新的向量数据库...")
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=persist_dir
)
return vectorstore
def setup_qa_chain(vectorstore):
"""设置问答链"""
# 使用 Ollama 运行 LLM
llm = Ollama(
model=LLM_MODEL,
base_url="http://localhost:11434",
temperature=0.7
)
# 从 LangChain Hub 获取检索增强生成提示模板
retrieval_qa_prompt = hub.pull("langchain-ai/retrieval-qa-string-template")
# 创建文档组合链
combine_docs_chain = create_stuff_documents_chain(
llm,
retrieval_qa_prompt
)
# 创建检索链
retrieval_chain = create_retrieval_chain(
vectorstore.as_retriever(),
combine_docs_chain
)
return retrieval_chain
def main():
"""主函数"""
print("=" * 50)
print("本地 RAG 问答系统")
print("=" * 50)
# 步骤 1: 加载文档
print("\n[1/4] 加载文档...")
documents = load_documents(DOCS_DIR)
# 步骤 2: 分割文档
print("\n[2/4] 分割文档...")
chunks = split_documents(documents)
# 步骤 3: 创建向量数据库
print("\n[3/4] 创建向量数据库...")
vectorstore = create_vectorstore(chunks, VECTORSTORE_DIR)
# 步骤 4: 设置问答系统
print("\n[4/4] 初始化问答系统...")
qa_chain = setup_qa_chain(vectorstore)
# 交互式问答
print("\n" + "=" * 50)
print("问答系统已就绪!输入问题开始咨询,输入 quit 退出")
print("=" * 50)
while True:
question = input("\n问题: ").strip()
if question.lower() in ["quit", "exit", "q"]:
print("再见!")
break
if not question:
continue
print("\n思考中...")
response = qa_chain.invoke({"input": question})
print("\n" + "-" * 50)
print("回答:")
print(response["answer"])
print("-" * 50)
# 显示参考来源
if "context" in response:
print("\n参考来源:")
for i, doc in enumerate(response["context"], 1):
source = doc.metadata.get("source", "未知")
print(f" {i}. {source}")
if __name__ == "__main__":
main()
准备测试文档
在 docs 目录下建个 about.txt:
# 公司介绍
我们的公司成立于 2020 年,专注于人工智能技术的研发和应用。
# 产品服务
我们提供以下核心产品:
1. 智能客服系统 - 基于大语言模型的客户服务解决方案
2. 数据分析平台 - 企业级数据分析工具
3. 自动化测试框架 - 软件质量保障工具
# 联系方式
邮箱: contact@example.com
电话: 400-123-4567
地址: 北京市海淀区科技园区
运行结果
跑起来之后,系统会按顺序执行:
==================================================
本地 RAG 问答系统
==================================================
[1/4] 加载文档...
已加载 1 个文档
[2/4] 分割文档...
文档分割完成,共 8 个文本块
[3/4] 创建向量数据库...
创建新的向量数据库...
[4/4] 初始化问答系统...
==================================================
问答系统已就绪!输入问题开始咨询,输入 quit 退出
==================================================
问题: 你们公司提供什么产品?
思考中...
--------------------------------------------------
回答:
根据提供的信息,我们公司提供以下核心产品:
1. 智能客服系统 - 基于大语言模型的客户服务解决方案
2. 数据分析平台 - 企业级数据分析工具
3. 自动化测试框架 - 软件质量保障工具
这些产品覆盖了 AI 应用、数据分析和软件质量保障等领域。
--------------------------------------------------
参考来源:
1. docs/about.txt
可以看到,系统正常回答了关于公司产品的问题,而且准确引用了知识来源。
总结
这篇文章演示了怎么用 Ollama、LangChain 和 Chroma 搭本地 RAG。几个好处:
- 数据全在本地,不用传到云上,企业隐私有保障
- 不依赖外部 API,断网也能跑
- 开源模型随便选,根据自己机器配置挑
后续可以捣鼓的方向:换更大更专业的模型改善回答质量;调一调文本分割策略提高检索准度;加上对话历史支持多轮问答;包装成 API 给其他服务调用。
这个本地 RAG 方案给企业和开发者提供了一个安全、私有的 AI 知识管理选择。