不用联网、不用付费 API,用 Ollama 本地模型 + LangChain 搭建一个能读取 PDF/Markdown 并回答问题的文档问答系统。隐私安全,完全免费。
手上有几十份产品文档、技术手册或者合同 PDF,每次找资料都得翻半天。
能不能让 AI 帮你读?而且不用把文件传到云端——数据留在自己电脑上,安全又省事。
这个项目用 Ollama(本地大模型运行器)+ LangChain(AI 应用框架)搭一个文档问答系统。你丢一堆 PDF 进去,然后直接问问题,它会检索相关内容并回答。全程离线,零成本。
项目背景
企业文档、个人笔记、技术手册……这些数据往往不能上传到公共 AI 服务。用云端 API 有隐私风险,而且每次调用都要花钱。
本地方案的优势很明显:
- 数据不出本机,敏感文档放心用
- 一次安装,永久免费
- 支持多种格式:PDF、Markdown、TXT
- 可以换不同模型,按需调整速度和精度
技术选型
| 组件 | 选型 | 理由 |
|---|---|---|
| 本地模型 | Ollama + qwen2.5:7b | 中文理解好,7B 参数在普通笔记本上跑得动 |
| 向量数据库 | ChromaDB | 零配置,内存存储,适合个人项目 |
| 嵌入模型 | nomic-embed-text | 轻量级文本嵌入,中英文通用 |
| 框架 | LangChain | 封装了 RAG 全流程,上手快 |
| PDF 解析 | PyMuPDF | 速度快,支持扫描件 OCR |
全部依赖可以用 pip 一行装完。
实现步骤
第一步:安装 Ollama 和模型
先去 ollama.com 下载安装包。装好后打开终端:
# 下载并运行 qwen2.5 模型(中文能力强)
ollama pull qwen2.5:7b
# 下载嵌入模型
ollama pull nomic-embed-text
# 验证模型是否就绪
ollama list
启动 Ollama 服务(默认监听 11434 端口):
ollama serve &
第二步:安装 Python 依赖
pip install langchain langchain-community langchain-ollama chromadb pymupdf python-dotenv
第三步:编写文档问答脚本
在项目目录下创建 docs_qa.py:
import os
import sys
from pathlib import Path
from langchain_community.document_loaders import PyMuPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.vectorstores import Chroma
from langchain_ollama import OllamaLLM
from langchain.chains import RetrievalQA
# 配置
MODEL_NAME = "qwen2.5:7b"
EMBEDDING_MODEL = "nomic-embed-text"
VECTOR_DB_PATH = "./chroma_db"
DOCS_DIR = "./documents"
def load_documents(doc_dir):
"""加载目录下的所有文档"""
documents = []
doc_path = Path(doc_dir)
if not doc_path.exists():
print(f"⚠️ 文档目录不存在:{doc_dir}")
print("创建目录并放入 PDF/TXT 文件后重试")
doc_path.mkdir(parents=True, exist_ok=True)
return documents
for file_path in doc_path.iterdir():
if file_path.suffix == ".pdf":
print(f"📄 加载 PDF:{file_path.name}")
loader = PyMuPDFLoader(str(file_path))
documents.extend(loader.load())
elif file_path.suffix in (".txt", ".md"):
print(f"📝 加载文本:{file_path.name}")
loader = TextLoader(str(file_path))
documents.append(loader.load()[0])
print(f"✅ 共加载 {len(documents)} 个文档块")
return documents
def build_vector_store(docs):
"""切片文档并构建向量索引"""
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = splitter.split_documents(docs)
print(f"🔪 切分为 {len(chunks)} 个片段")
embeddings = OllamaEmbeddings(
model=EMBEDDING_MODEL,
base_url="http://localhost:11434"
)
vectorstore = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
persist_directory=VECTOR_DB_PATH
)
vectorstore.persist()
print(f"💾 向量索引已保存至 {VECTOR_DB_PATH}")
return vectorstore
def ask_question(vectorstore, question):
"""向文档提问"""
llm = OllamaLLM(
model=MODEL_NAME,
base_url="http://localhost:11434",
temperature=0.2,
num_ctx=4096
)
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
)
result = qa_chain.invoke({"query": question})
print(f"\n❓ 问题:{question}")
print(f"🤖 回答:{result['result']}")
print(f"\n📎 引用来源:")
for doc in result["source_documents"][:2]:
print(f" - {doc.metadata.get('source', '未知')}")
def main():
print("=" * 50)
print("📚 本地文档问答机器人")
print("=" * 50)
# 加载文档
docs = load_documents(DOCS_DIR)
if not docs:
print("没有可处理的文档,请先将文件放入 documents/ 目录")
sys.exit(1)
# 构建向量库
vectorstore = build_vector_store(docs)
# 交互式问答
print("\n开始提问(输入 'quit' 退出):")
while True:
try:
question = input("\n❓ ").strip()
except (EOFError, KeyboardInterrupt):
print("\n再见!")
break
if not question:
continue
if question.lower() in ("quit", "exit", "退出"):
print("再见!")
break
ask_question(vectorstore, question)
if __name__ == "__main__":
main()
第四步:准备文档并运行
创建一个测试用的 Markdown 文件:
mkdir -p documents
cat > documents/sample.md << 'EOF'
# 产品使用手册 v2.0
## 快速开始
1. 注册账号后进入控制台
2. 在「项目设置」中创建新项目
3. 获取 API Key 并开始调用接口
## 计费说明
基础版每月免费额度 1000 次调用。超出部分按 0.01 元/次计费。
## 常见问题
Q: API 响应超时怎么办?
A: 检查网络连接,确认请求体不超过 1MB。如仍超时请提交工单。
EOF
运行脚本:
python docs_qa.py
首次运行会加载文档、建立向量索引(大概 10-20 秒)。之后再次运行会直接复用缓存,几乎秒开。
交互界面像这样:
📚 本地文档问答机器人
==================================================
📄 加载 PDF:xxx.pdf
📝 加载文本:sample.md
✅ 共加载 2 个文档块
🔪 切分为 15 个片段
💾 向量索引已保存至 ./chroma_db
开始提问(输入 'quit' 退出):
❓ API 超时时应该怎么办?
🤖 回答:API 响应超时时,建议先检查网络连接,确认请求体不超过 1MB。如果问题仍然存在,可以提交工单寻求帮助。
📎 引用来源:
- documents/sample.md
第五步:批量处理真实文档
如果你有多个 PDF 需要处理,把文件放进 documents/ 目录即可。脚本会自动扫描所有支持的格式。
想要更精细的控制,可以在脚本里加一个配置项:
# 在 load_documents 函数中添加过滤逻辑
EXCLUDE_PATTERNS = ["草稿", "废弃", "临时"]
for file_path in doc_path.iterdir():
if any(pattern in file_path.name for pattern in EXCLUDE_PATTERNS):
print(f"⏭️ 跳过:{file_path.name}")
continue
# ... 正常加载
运行效果
跑起来之后,你可以:
- 把产品手册丢进去,客服团队直接问 AI 拿答案
- 导入技术文档,开发查接口参数不再翻 Wiki
- 把合同和法规放进去,法务快速定位条款
- 配合 cron 定时任务,每天自动更新文档索引
整个流程从安装到能用,大概 15 分钟。硬件要求不高——8GB 内存的笔记本就能跑 7B 模型。
优化方向
这个项目是个可用的原型,接下来可以往这些方向打磨:
- 流式输出:加上
stream=True参数,回答不再是干等几秒后一次性吐出,而是逐字显示,体验更像聊天 - 多轮对话:引入 ConversationBufferMemory,支持上下文追问,不用每次都重复背景
- 混合搜索:ChromaDB 默认是向量相似度搜索,加上关键词 BM25 混合检索,召回率更高
- Web 界面:用 Streamlit 或 Gradio 套一层 UI,同事也能直接用浏览器访问
- 模型升级:硬件够的话换成 qwen2.5:14b 或 llama3.1:8b,回答质量会有明显提升
- 增量更新:记录文件修改时间,只重新索引变更过的文档,节省计算资源
文档放在本地,问答跑在本地,AI 能力也跑在本地。这就是完全属于自己的私人知识库。
要不要试试把你手头那份最厚的 PDF 喂给它?
这篇文章对你有帮助吗?