# 使用 LangChain + Ollama 构建本地 RAG 应用
## 背景介绍
大语言模型很聪明,但它不知道你公司内部的事情。你问它”我们上季度的技术方案是什么”,它只能道歉。这就是 RAG(检索增强生成)要解决的问题——先从你的知识库里找到相关文档,再把内容喂给模型让它回答。
搭建 RAG 通常得依赖云端 API,数据要上传到 OpenAI 或 Anthropic 的服务器。有些公司不乐意,个人项目也不想花这笔钱。Ollama 出来后,在本地跑大模型变得很方便。LangChain 配套的组件足够完善,两者一结合,一个完全本地运行的 RAG 系统就出来了。
## 问题描述
实际做的时候会遇到几个具体问题:
**接口版本**:LangChain 现在的版本是 0.2.x,但网上很多教程还是 0.1.x 的写法。`langchain_community` 和 `langchain_ollama` 的调用方式不一样,用错了就会报错。
**向量模型**:文本要转成向量才能做相似性搜索。开源的 embedding 模型不少,Ollama 自带的 `nomic-embed-text` 足够用了,不用再额外部署 sentence-transformers。
**文档格式**:PDF、Word、Markdown 都得处理。分块是技术活,块太大了检索不准,太小了上下文不完整。
**检索效果**:光靠语义相似有时不够干净,可能需要 reranking 或者混合关键词搜索。
## 详细步骤
### 1. 环境准备
Ollama 安装很简单。macOS 用 Homebrew:
“`bash
brew install ollama
ollama serve
“`
Linux 直接跑安装脚本:
“`bash
curl -fsSL https://ollama.com/install.sh | sh
“`
装好后拉模型:
“`bash
ollama pull mistral
ollama pull nomic-embed-text
“`
Python 环境:
“`bash
python -m venv venv
source venv/bin/activate
pip install langchain langchain-community langchain-ollama langchain-text-splitters langchain-huggingface faiss-cpu beautifulsoup4 requests
“`
注意 `langchain-ollama` 是官方包,0.1.x 和 0.2.x 的 import 路径可能不一样,用的时候注意一下版本。
### 2. 实现 RAG 流水线
新建 `rag_pipeline.py`,实现文档加载、分块、向量存储和检索。
#### 2.1 文档加载与处理
“`python
import os
from pathlib import Path
from typing import List
from langchain_community.document_loaders import (
TextLoader,
PyPDFLoader,
UnstructuredMarkdownLoader,
BSHTMLLoader
)
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_ollama import OllamaEmbeddings
class DocumentProcessor:
“””文档处理器:加载和分块文档”””
def __init__(self, chunk_size: int = 500, chunk_overlap: int = 50):
self.chunk_size = chunk_size
self.chunk_overlap = chunk_overlap
self.text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=[“\n\n”, “\n”, “。”, “!”, “?”, ” “, “”]
)
def load_document(self, file_path: str):
“””根据文件类型选择合适的加载器”””
path = Path(file_path)
suffix = path.suffix.lower()
loaders = {
.txt: TextLoader,
.md: UnstructuredMarkdownLoader,
.pdf: PyPDFLoader,
.html: BSHTMLLoader,
.htm: BSHTMLLoader
}
loader_class = loaders.get(suffix, TextLoader)
loader = loader_class(file_path, encoding=”utf-8″)
return loader.load()
def process_file(self, file_path: str) -> List[str]:
“””加载并分块文档”””
documents = self.load_document(file_path)
chunks = self.text_splitter.split_documents(documents)
return chunks
“`
这段代码支持 txt、md、pdf、html 几种常见格式。`RecursiveCharacterTextSplitter` 按段落和句子递归切分,`chunk_size=500` 表示每块大约 500 字符,`chunk_overlap=50` 让相邻块有 50 字符重叠,防止关键信息被切断。
#### 2.2 向量存储与检索
“`python
from langchain.vectorstores import FAISS
from langchain_ollama import OllamaLLM
class RAGSystem:
“””RAG 系统核心类”””
def __init__(self, embed_model: str = “nomic-embed-text”,
llm_model: str = “mistral”):
# 初始化本地嵌入模型
self.embeddings = OllamaEmbeddings(
model=embed_model,
base_url=”http://localhost:11434″
)
# 初始化本地 LLM
self.llm = OllamaLLM(
model=llm_model,
base_url=”http://localhost:11434″
)
self.vectorstore = None
self.processor = DocumentProcessor()
def build_index(self, documents: List[str]):
“””构建向量索引”””
self.vectorstore = FAISS.from_texts(
documents,
embedding=self.embeddings
)
print(f”索引构建完成,共 {len(documents)} 个文档块”)
def build_index_from_files(self, file_paths: List[str]):
“””从多个文件构建索引”””
all_chunks = []
for file_path in file_paths:
chunks = self.processor.process_file(file_path)
all_chunks.extend([chunk.page_content for chunk in chunks])
self.build_index(all_chunks)
return self
def retrieve(self, query: str, k: int = 3) -> List[str]:
“””检索相关文档”””
if self.vectorstore is None:
raise ValueError(“请先构建索引”)
docs = self.vectorstore.similarity_search(query, k=k)
return [doc.page_content for doc in docs]
def answer(self, query: str, k: int = 3) -> str:
“””检索并生成答案”””
# 检索相关文档
relevant_docs = self.retrieve(query, k)
# 构建提示词
context = “\n\n”.join(relevant_docs)
prompt = f”””基于以下参考文档回答问题。如果文档中没有相关信息,请如实说明。
参考文档:
{context}
问题:{query}
回答:”””
# 调用本地 LLM
response = self.llm.invoke(prompt)
return response
“`
FAISS 是 Facebook 开发的向量检索库,速度快且支持多种索引类型。`OllamaEmbeddings` 调用本地的 nomic-embed-text 生成向量,`OllamaLLM` 调用本地的 mistral 生成回答。检索结果通过 prompt 注入上下文。
#### 2.3 运行示例
`main.py` 脚本演示完整流程:
“`python
from rag_pipeline import RAGSystem
def main():
# 创建 RAG 系统
rag = RAGSystem(
embed_model=”nomic-embed-text”,
llm_model=”mistral”
)
# 准备示例文档
docs = [
“LangChain 是一个用于构建 LLM 应用的框架。”,
“它提供了丰富的组件,包括模型调用、提示词管理和链式调用。”,
“Ollama 是一个在本地运行大语言模型的工具。”,
“它支持多种模型,如 Llama、Mistral 和 CodeLlama。”,
“RAG 是检索增强生成的缩写,是一种让 LLM 访问私有知识库的技术。”,
“向量数据库用于存储文本的向量表示,支持高效相似性搜索。”
]
# 构建索引
print(“正在构建向量索引…”)
rag.build_index(docs)
# 测试问答
test_queries = [
“LangChain 是什么?”,
“Ollama 支持哪些模型?”,
“RAG 的全称是什么?”
]
print(“\n” + “=”*60)
print(“开始问答测试”)
print(“=”*60)
for query in test_queries:
print(f”\n问题: {query}”)
print(“-” * 40)
answer = rag.answer(query)
print(f”回答: {answer}”)
if __name__ == “__main__”:
main()
“`
## 运行结果
执行 `python main.py`:
“`
正在构建向量索引…
索引构建完成,共 6 个文档块
============================================================
开始问答测试
============================================================
问题: LangChain 是什么?
—————————————-
回答: LangChain 是一个用于构建 LLM 应用的框架,它提供了丰富的组件,包括模型调用、提示词管理和链式调用。
问题: Ollama 支持哪些模型?
—————————————-
回答: Ollama 支持多种模型,如 Llama、Mistral 和 CodeLlama。
问题: RAG 的全称是什么?
—————————————-
回答: RAG 是检索增强生成的缩写(Retrieval-Augmented Generation),是一种让 LLM 访问私有知识库的技术。
“`
系统成功检索了相关文档并生成了正确答案。检索到的内容与问题匹配,LLM 基于这些上下文给出了准确的回答。
如果遇到 “connection refused” 错误,说明 Ollama 服务没启动。确保先运行 `ollama serve`,然后用 `ollama list` 确认模型已下载。
## 总结
这篇文章写了用 LangChain + Ollama 搭建本地 RAG 的全过程。核心就两个东西:Ollama 提供本地模型能力,LangChain 负责把检索和生成串起来。数据全程不用上网,存在自己机器上。
几个关键点:
**轻量**:不需要额外的向量数据库服务。FAISS 索引就是几个文件,复制粘贴就能迁移。
**模型选择**:7B 参数的量化模型在普通 GPU 上就能跑,内存要求不算高。mistral 和 llama3 都行,看具体需求。
**可扩展**:现在是最基础的向量检索,后面可以加混合搜索、加 reranking、加对话历史管理。
后续可以优化的方向:用 bge-m3 这种更强embedding模型、加入关键词索引做混合搜索、对检索结果做 reranking、支持更多文档格式、做多轮对话。
完整代码扔在 GitHub 上了,有需要自取。希望这篇文章对你有帮助。