背景介绍
很多企业在做内部文档问答时,第一反应是把文件丢给 ChatGPT 或者 Claude。这确实方便,但把公司内部资料上传到第三方服务这件事,足以让法务部门跳起来。数据隐私这事儿不是闹着玩的。
Ollama 出现之后,情况变了。我们可以在自己的电脑上跑大语言模型,配合 LangChain 的 RAG 框架,整个问答系统完全可以私有化部署。本文记录的就是这个过程,从环境搭建到跑通第一个问答。
问题描述
实际需求很具体:
- 数据不能出门:公司内部的敏感文档不可能上传到云端
- 离线也要能用:有些场景完全没网络,必须本地提供服务
- 省钱:按 token 收费的 API 调用,长期看是一笔不小的开销
- 垂直领域:需要针对特定业务领域做优化,通用模型有时答不到点子上
把文档喂给云端 API 是最省事的办法,但隐私风险始终是个坎。RAG 的思路是先把文档转成向量存到本地,然后只把用户的问题发送给本地的大模型。这样既保住了数据安全,又能用到 LLM 的能力。
详细步骤
环境准备
依赖包就这些,基本都是常规操作:
# 创建虚拟环境
python -m venv rag_env
source rag_env/bin/activate
# 安装依赖
pip install langchain langchain-community langchain-chroma ollama chromadb beautifulsoup4 pypdf pydantic
电脑内存建议 16GB 以上,磁盘空间看你要跑多大的模型。
第一步:下载并运行 Ollama
Ollama 支持 macOS、Linux 和 Windows。Linux 安装比较简单:
# 安装 Ollama
curl -fsSL https://ollama.com/install.sh | sh
# 看看已经有哪些模型
ollama list
# 拉取 LLaMA 3,8B 版本大概 4.7GB
ollama pull llama3
# 中文效果更好的话,可以试试 qwen
ollama pull qwen:7b
# 启动服务,默认端口 11434
ollama serve
另一个终端试试看服务有没有跑起来:
curl http://localhost:11434/api/generate -d '{
"model": "llama3",
"prompt": "Hello",
"stream": false
}'
返回一串 JSON 就说明没问题。
第二步:准备文档加载器
LangChain 的文档加载器支持 PDF、txt、Markdown、网页等各种格式。下面是加载 PDF 和纯文本文件的代码:
from langchain_community.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
def load_documents(file_paths):
documents = []
for file_path in file_paths:
if file_path.endswith('.pdf'):
loader = PyPDFLoader(file_path)
elif file_path.endswith('.txt'):
loader = TextLoader(file_path, encoding='utf-8')
elif file_path.endswith('.md'):
loader = TextLoader(file_path, encoding='utf-8')
else:
continue
documents.extend(loader.load())
return documents
def split_documents(documents, chunk_size=1000, chunk_overlap=200):
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", " ", ""]
)
return text_splitter.split_documents(documents)
文档不能直接丢给向量数据库,必须先拆成小块。这个步骤叫做 chunking,分块的大小直接影响后续检索的效果。
第三步:创建向量存储
Chroma 是纯本地的向量数据库,整个过程不依赖任何外部服务:
from langchain_chroma import Chroma
from langchain_ollama import OllamaEmbeddings
def create_vector_store(documents, persist_directory="./chroma_db"):
embeddings = OllamaEmbeddings(
model="nomic-embed-text",
base_url="http://localhost:11434"
)
vector_store = Chroma.from_documents(
documents=documents,
embedding=embeddings,
persist_directory=persist_directory
)
return vector_store
别忘了先下载嵌入模型:
ollama pull nomic-embed-text
嵌入模型把文本转成向量,这些向量存储在 Chroma 里。检索的时候,用户的问题也会被转成向量,然后通过向量相似度找到最相关的文档块。
第四步:组装 RAG 链
把各个部分拼起来,形成完整的问答流程:
from langchain_ollama import ChatOllama
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain_core.prompts import ChatPromptTemplate
def build_qa_chain(vector_store):
llm = ChatOllama(
model="llama3",
base_url="http://localhost:11434",
temperature=0.7
)
system_prompt = """你是一个专业的问答助手。请根据以下上下文来回答用户的问题。
上下文:
{context}
请用中文回答问题。"""
prompt = ChatPromptTemplate.from_messages([
("system", system_prompt),
("human", "{input}")
])
document_chain = create_stuff_documents_chain(llm, prompt)
retriever = vector_store.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
retrieval_chain = create_retrieval_chain(retriever, document_chain)
return retrieval_chain
这里 temperature 参数控制输出的随机性。0.7 是比较平衡的值,既有一定创意,又不会太离谱。
第五步:跑起来
完整代码如下:
def main():
documents = load_documents(["./docs/技术文档.pdf", "./docs/产品说明.txt"])
chunks = split_documents(documents)
print(f"文档已分割为 {len(chunks)} 个块")
vector_store = create_vector_store(chunks, persist_directory="./chroma_db")
qa_chain = build_qa_chain(vector_store)
print("=" * 50)
print("RAG 问答系统已启动!输入问题开始查询,输入 quit 退出")
print("=" * 50)
while True:
query = input("\n问题: ")
if query.lower() in ['quit', 'exit', 'q']:
print("再见!")
break
result = qa_chain.invoke({"input": query})
print("\n回答:", result["answer"])
if __name__ == "__main__":
main()
运行结果
第一次跑的时候,输出大概是这样:
文档已分割为 156 个块 ================================================== RAG 问答系统已启动!输入问题开始查询,输入 quit 退出 ================================================== 问题: 这款产品的主要功能是什么? ================================================== 回答: 根据提供的产品文档,这款产品的主要功能包括: 1. 数据处理:支持批量处理 CSV、JSON 和 Excel 格式的数据文件 2. 自动化报告:可以根据预设模板自动生成日报、周报和月报 3. API 集成:提供 RESTful API 接口,支持与第三方系统对接 4. 用户管理:支持多用户协作和权限管理 ================================================== 参考来源: [1] 产品支持多种数据格式导入,包括 CSV、JSON... [2] 自动化报告功能可以帮助用户节省... [3] 系统提供完整的 API 接口文档...
可以看到,系统从本地文档里找到了相关内容,并基于这些内容生成了回答。整个过程都是本地运行的,没有任何数据流出。
总结
用 Ollama + LangChain 搭建本地 RAG 系统,核心优势就四点:
- 数据完全可控:文档存在自己电脑上,不涉及任何第三方服务
- 没有 API 费用:模型跑在本地,一次投入长期使用
- 离线也能用:断了网依然能回答问题
- 可以深度定制:针对特定领域可以换不同的模型,或者调整检索策略
缺点也有:本地模型的响应速度通常不如云端 API,特别是大模型。如果对延迟敏感,可以考虑用量化版本的模型,或者升级硬件配置。
完整代码就这些,改改路径和模型名称就能直接用。后续可以探索的方向还有:流式输出、多轮对话、对话历史持久化、集成更多文档格式支持等。