背景介绍
大语言模型现在很火,但直接用云端 API 有不少问题。数据要传出去、公司机密可能泄露,每个月调用费用也是笔开销。最麻烦的是网络一不稳定,整个服务就挂掉。
Ollama 这东西让在本地跑 LLM 变成了现实。它支持 Llama 2、Mistral、Gemma 这些开源模型,macOS 和 Linux 都能装,一行命令就把模型拉下来用。
光有模型不够用。有时候我们需要让 AI 学特定领域的知识,比如公司内部文档、产品手册,这时候 RAG(检索增强生成)就派上用场了。它把外部知识库和 LLM 结合起来,能用我们自己的数据来回答问题。
这篇文章就说清楚:怎么在普通电脑上搭建一个完整的 RAG 系统,用 Ollama 提供模型,LangChain 处理文档、抽文本、存向量、最后问答。
问题描述
几个常见的真实需求:
- 企业内部有大量内部文档、技术手册、产品资料,想做个 AI 助手来帮员工查东西。
- 文档散落在各处,想把它们整成一个统一的问答系统,用自然语言来查。
- 数据敏感或者网络受限,必须在本地跑,不能用任何第三方云服务。
以前要实现这些,得买 GPU 服务器,或者花大钱买商业 API。现在消费级电脑也能跑了,Ollama + LangChain 这套组合就是解决方案。
核心问题就一个:怎么把本地的 PDF、Markdown、TXT 这些文档变成可检索的知识库,再基于知识库做出问答系统。
详细步骤
环境准备
先装好需要的软件。
# Ollama 安装(macOS 或 Linux)
curl -fsSL https://ollama.com/install.sh | sh
# 拉取模型
ollama pull llama2
# 创建项目目录
mkdir -p ~/rag-demo && cd ~/rag-demo
# 创建 Python 虚拟环境
python -m venv venv
source venv/bin/activate # Linux/macOS 用这句
# Windows 上用: venv\Scripts\activate
# 安装依赖包
pip install langchain langchain-community langchain-openai \
chromadb beautifulsoup4 pypdf pydantic \
sentence-transformers
核心流程
RAG 系统实际就绕着这几个步骤转:
- 文档加载:从 PDF、Markdown、TXT 这些文件里把文字内容抽出来
- 文本分块:把长文章切成小段
- 向量化:把小段文字转成向量
- 向量存库:把向量存到数据库里,方便做相似性搜索
- 问答检索:用户问了问题,去库里找相关的文档片段
- 生成回答:把找到的文档和用户问题一起发给 LLM,让它生成答案
完整代码示例
1. 配置和初始化
import os
from langchain_community.chat_models import ChatOllama
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.document_loaders import (
PyPDFLoader,
TextLoader,
MarkdownLoader
)
from langchain.chains import RetrievalQA
# 初始化 Ollama 模型
llm = ChatOllama(
model="llama2",
temperature=0.7,
base_url="http://localhost:11434"
)
# 向量化模型配置
embeddings = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={device: cpu}
)
2. 文档加载
from pathlib import Path
def load_documents(directory: str):
"""遍历目录,把所有能识别的文档都加载进来"""
documents = []
path = Path(directory)
for file_path in path.rglob("*"):
if file_path.is_file():
try:
if file_path.suffix == .pdf:
loader = PyPDFLoader(str(file_path))
documents.extend(loader.load())
elif file_path.suffix == .txt:
loader = TextLoader(str(file_path), encoding=utf-8)
documents.extend(loader.load())
elif file_path.suffix == .md:
loader = MarkdownLoader(str(file_path))
documents.extend(loader.load())
except Exception as e:
print(f"加载失败 {file_path}: {e}")
return documents
# 调用示例
docs = load_documents("./knowledge_base")
print(f"共加载 {len(docs)} 个文档")
3. 文本分块
def split_documents(documents, chunk_size=1000, chunk_overlap=200):
"""把大段文字切成小块,方便后面做检索"""
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
length_function=len,
separators=["\n\n", "\n", "。", " ", ""]
)
return text_splitter.split_documents(documents)
# 执行分块
chunks = split_documents(docs)
print(f"切成了 {len(chunks)} 个小块")
4. 向量存储
def create_vector_store(chunks, embeddings, persist_directory="./chroma_db"):
"""建向量数据库,把文本块转成的向量存进去"""
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=persist_directory
)
return vectorstore
# 创建向量库
vectorstore = create_vector_store(chunks, embeddings)
print("向量数据库好了")
5. 问答链
def create_qa_system(llm, vectorstore):
"""把检索和 LLM 连起来,做成问答系统"""
retriever = vectorstore.as_retriever(
search_type="similarity",
search_kwargs={"k": 3}
)
qa_chain = RetrievalQA.from_chain_type(
llm=llm,
chain_type="stuff",
retriever=retriever,
return_source_documents=True
)
return qa_chain
# 初始化问答系统
qa_system = create_qa_system(llm, vectorstore)
6. 主程序示例
def main():
knowledge_dir = "./knowledge_base"
# 第一次跑,建个测试用的知识库
if not os.path.exists(knowledge_dir):
print("生成测试文档...")
os.makedirs(knowledge_dir)
sample_content = """
# 产品功能介绍
智能助手有这些核心功能:
1. 文本生成:能写各种类型的文章和内容
2. 问答系统:根据知识库里的资料回答问题
3. 翻译:多语言互译
4. 编程辅助:帮忙写代码和调试
联系渠道:
邮箱:support@example.com
电话:400-123-4567
"""
with open(os.path.join(knowledge_dir, "产品介绍.md"), "w", encoding="utf-8") as f:
f.write(sample_content)
# 加载文档
print("加载文档中...")
documents = load_documents(knowledge_dir)
print(f"加载了 {len(documents)} 个")
# 切分
print("切分文档...")
text_chunks = split_documents(documents)
print(f"切完 {len(text_chunks)} 个块")
# 建向量库
print("建向量库...")
vector_store = create_vector_store(text_chunks, embeddings)
# 初始化问答
print("问答系统好了...")
qa = create_qa_system(llm, vector_store)
# 进入交互
print("\n" + "="*50)
print("RAG 系统跑起来了!")
print("="*50)
while True:
question = input("\n问点啥(输入 q 退出): ")
if question.lower() == q:
break
result = qa({"query": question})
print(f"\n回答: {result[result]}")
print(f"\n参考来源:")
for i, doc in enumerate(result[source_documents], 1):
print(f" {i}. {doc.metadata.get(source, 未知来源)}")
if __name__ == "__main__":
main()
运行结果
跑起来是这样的:
==================================================
RAG 系统跑起来了!
==================================================
问点啥(输入 q 退出): 产品有哪些功能?
回答: 根据资料,产品有这些功能:
1. 文本生成 - 能写各种类型的文章和内容
2. 问答系统 - 根据知识库里的资料回答问题
3. 翻译 - 多语言互译
4. 编程辅助 - 帮忙写代码和调试
参考来源:
1. ./knowledge_base/产品介绍.md
答案不仅准确,还能看到从哪个文件来的。RAG 就是这个原理:先去库里找相关的,再把找到的内容喂给 LLM 生成答案。
问点啥(输入 q 退出): 联系方式是什么?
回答: 联系邮箱是 support@example.com,电话是 400-123-4567
即使是文档里的具体信息,也能准确定位和提取出来。
总结
这套方案能让你在本地跑一个完整的 RAG 系统。几个明显的好处:
- 数据不用出去,全部在本地处理
- 成本就一台电脑的事,跑起来不用网络,断网也能用
- 可以自己选模型、改参数,满足各种奇怪的需求
- 模块都拆开了,加新文档、加功能都很方便
代码结构不复杂,就是文档加载、切分、建向量库、问答这几步。个人用够了。Production 环境里可能要考虑的:多用户并发、知识库更新、更多文件格式支持,但那是后话。
私有化的 LLM + RAG,这组合现在真能打了。手里有资料想做个 AI 助手的,直接抄这套就行。